Spaces:
Sleeping
Sleeping
| # Google ADK 핵심 개념 완전 가이드 | |
| 이 문서는 Google Agent Development Kit (ADK)의 핵심 개념들을 체계적으로 설명합니다. | |
| 샘플 코드에서 사용된 모든 중요 요소들을 이해할 수 있도록 구성했습니다. | |
| --- | |
| ## 📚 목차 | |
| 1. [전체 아키텍처 개요](#1-전체-아키텍처-개요) | |
| 2. [Session (세션)](#2-session-세션) | |
| 3. [State (상태)](#3-state-상태) | |
| 4. [Event (이벤트)](#4-event-이벤트) | |
| 5. [Context (컨텍스트)](#5-context-컨텍스트) | |
| 6. [Runner (러너)](#6-runner-러너) | |
| 7. [Memory (메모리)](#7-memory-메모리) | |
| 8. [Callbacks (콜백)](#8-callbacks-콜백) | |
| 9. [Tools (도구)](#9-tools-도구) | |
| 10. [핵심 개념 간의 관계](#10-핵심-개념-간의-관계) | |
| --- | |
| ## 1. 전체 아키텍처 개요 | |
| ### ADK의 핵심 철학 | |
| ADK는 **"에이전트 개발을 소프트웨어 개발처럼"** 만드는 것을 목표로 합니다. | |
| 복잡한 AI 에이전트 시스템을 모듈화하고, 테스트 가능하며, 유지보수하기 쉽게 만듭니다. | |
| ### 핵심 컴포넌트 다이어그램 | |
| ``` | |
| ┌─────────────────────────────────────────────────────────────────┐ | |
| │ 사용자 (User) │ | |
| └─────────────────────────────────────────────────────────────────┘ | |
| │ | |
| ▼ | |
| ┌─────────────────────────────────────────────────────────────────┐ | |
| │ Runner (러너/오케스트레이터) │ | |
| │ - 실행 루프 관리 │ | |
| │ - 이벤트 처리 및 라우팅 │ | |
| │ - 서비스 조율 │ | |
| └─────────────────────────────────────────────────────────────────┘ | |
| │ │ │ | |
| ▼ ▼ ▼ | |
| ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ | |
| │ SessionService │ │ MemoryService │ │ ArtifactService │ | |
| │ (세션 관리) │ │ (장기 기억) │ │ (파일/데이터) │ | |
| └──────────────────┘ └──────────────────┘ └──────────────────┘ | |
| │ | |
| ▼ | |
| ┌─────────────────────────────────────────────────────────────────┐ | |
| │ Session (세션) │ | |
| │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐ │ | |
| │ │ State │ │ Events │ │ Metadata (ID, User) │ │ | |
| │ │ (상태) │ │ (이벤트들) │ │ │ │ | |
| │ └─────────────┘ └─────────────┘ └─────────────────────────┘ │ | |
| └─────────────────────────────────────────────────────────────────┘ | |
| │ | |
| ▼ | |
| ┌─────────────────────────────────────────────────────────────────┐ | |
| │ Agent (에이전트) │ | |
| │ ┌─────────────────────────────────────────────────────────┐ │ | |
| │ │ InvocationContext (호출 컨텍스트) │ │ | |
| │ │ - session, state, services 접근 │ │ | |
| │ └─────────────────────────────────────────────────────────┘ │ | |
| │ │ │ | |
| │ ┌───────────────┼───────────────┐ │ | |
| │ ▼ ▼ ▼ │ | |
| │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ | |
| │ │ Tools │ │ LLM │ │Callbacks│ │ | |
| │ │ (도구) │ │ (모델) │ │ (콜백) │ │ | |
| │ └─────────┘ └─────────┘ └─────────┘ │ | |
| └─────────────────────────────────────────────────────────────────┘ | |
| ``` | |
| --- | |
| ## 2. Session (세션) | |
| ### 개념 | |
| **Session**은 사용자와 에이전트 간의 **단일 대화 스레드**를 나타냅니다. | |
| 전화 통화에 비유하면, 하나의 통화가 하나의 세션입니다. | |
| ### 주요 속성 | |
| ```python | |
| from google.adk.sessions import Session | |
| # Session의 주요 속성 | |
| session.id # 세션 고유 ID | |
| session.app_name # 애플리케이션 이름 | |
| session.user_id # 사용자 ID | |
| session.state # 상태 딕셔너리 (State) | |
| session.events # 이벤트 히스토리 리스트 | |
| session.last_update_time # 마지막 업데이트 시간 | |
| ``` | |
| ### SessionService | |
| 세션의 생명주기를 관리하는 서비스입니다. | |
| ```python | |
| from google.adk.sessions import InMemorySessionService | |
| # 세션 서비스 생성 | |
| session_service = InMemorySessionService() | |
| # 새 세션 생성 | |
| session = await session_service.create_session( | |
| app_name="my_app", | |
| user_id="user_123", | |
| state={"initial_key": "initial_value"} # 초기 상태 설정 가능 | |
| ) | |
| # 기존 세션 조회 | |
| session = await session_service.get_session( | |
| app_name="my_app", | |
| user_id="user_123", | |
| session_id="session_abc" | |
| ) | |
| # 세션 목록 조회 | |
| sessions = await session_service.list_sessions( | |
| app_name="my_app", | |
| user_id="user_123" | |
| ) | |
| # 세션 삭제 | |
| await session_service.delete_session( | |
| app_name="my_app", | |
| user_id="user_123", | |
| session_id="session_abc" | |
| ) | |
| ``` | |
| ### SessionService 구현체 종류 | |
| | 구현체 | 저장 위치 | 영속성 | 용도 | | |
| | ------------------------ | ------------ | ---------------------- | ------------- | | |
| | `InMemorySessionService` | 메모리 | ❌ (앱 재시작 시 소멸) | 개발/테스트 | | |
| | `DatabaseSessionService` | SQL DB | ✅ | 프로덕션 | | |
| | `VertexAISessionService` | Google Cloud | ✅ | 클라우드 배포 | | |
| ### 세션 생명주기 | |
| ``` | |
| ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ | |
| │ CREATE │ ──▶ │ UPDATE │ ──▶ │ DELETE │ | |
| │ (생성) │ │ (이벤트 추가)│ │ (종료) │ | |
| └─────────────┘ └─────────────┘ └─────────────┘ | |
| │ | |
| ▼ | |
| ┌─────────────┐ | |
| │ State │ | |
| │ 변경 │ | |
| └─────────────┘ | |
| ``` | |
| --- | |
| ## 3. State (상태) | |
| ### 개념 | |
| **State**는 세션 내에서 데이터를 저장하는 **키-값 저장소**입니다. | |
| 에이전트의 "메모장" 역할을 하며, 대화 중 필요한 정보를 기억합니다. | |
| ### State의 범위 (Prefix) | |
| ADK에서 State는 **prefix**로 범위를 구분합니다: | |
| | Prefix | 범위 | 설명 | | |
| | ------- | ------------ | --------------------------------------------- | | |
| | (없음) | 세션 전체 | 기본값, 세션 동안 유지 | | |
| | `app:` | 애플리케이션 | 같은 앱의 모든 세션에서 공유 | | |
| | `user:` | 사용자 | 같은 사용자의 모든 세션에서 공유 | | |
| | `temp:` | 현재 호출 | 현재 invocation에서만 유지, 호출 종료 시 삭제 | | |
| ```python | |
| # State 사용 예시 | |
| ctx.session.state["user_name"] = "홍길동" # 세션 범위 | |
| ctx.session.state["app:settings"] = {"theme": "dark"} # 앱 전체 공유 | |
| ctx.session.state["user:preferences"] = {"lang": "ko"} # 사용자 공유 | |
| ctx.session.state["temp:current_step"] = 3 # 현재 호출만 | |
| ``` | |
| ### State 읽기/쓰기 | |
| #### 1. output_key를 통한 자동 저장 (LlmAgent) | |
| ```python | |
| from google.adk.agents import LlmAgent | |
| # 에이전트 응답이 자동으로 state['analysis_result']에 저장됨 | |
| agent = LlmAgent( | |
| name="Analyzer", | |
| instruction="데이터를 분석하세요.", | |
| output_key="analysis_result" # ← 자동 저장 | |
| ) | |
| ``` | |
| #### 2. Instruction 템플릿에서 읽기 | |
| ```python | |
| agent = LlmAgent( | |
| name="Reporter", | |
| instruction=""" | |
| 이전 분석 결과: {analysis_result} | |
| 위 결과를 바탕으로 보고서를 작성하세요. | |
| """ # ← {key} 형식으로 자동 주입 | |
| ) | |
| ``` | |
| #### 3. Tool/Callback에서 직접 접근 | |
| ```python | |
| from google.adk.tools import ToolContext | |
| def my_tool(context: ToolContext, query: str) -> dict: | |
| # 읽기 | |
| user_name = context.state.get("user_name", "Unknown") | |
| # 쓰기 (자동으로 state_delta에 추적됨) | |
| context.state["last_query"] = query | |
| context.state["query_count"] = context.state.get("query_count", 0) + 1 | |
| return {"result": f"Hello {user_name}, processed: {query}"} | |
| ``` | |
| ### ⚠️ State 수정 시 주의사항 | |
| ```python | |
| # ❌ 잘못된 방법: 세션 객체 직접 수정 | |
| session = await session_service.get_session(...) | |
| session.state["key"] = "value" # 추적되지 않음! | |
| # ✅ 올바른 방법: Context를 통해 수정 | |
| def my_callback(context: CallbackContext): | |
| context.state["key"] = "value" # 자동 추적됨 | |
| ``` | |
| --- | |
| ## 4. Event (이벤트) | |
| ### 개념 | |
| **Event**는 세션 내에서 발생하는 **모든 활동의 기록 단위**입니다. | |
| 사용자 메시지, 에이전트 응답, 도구 호출, 상태 변경 등 모든 것이 이벤트입니다. | |
| ### Event 구조 | |
| ```python | |
| from google.adk.events import Event, EventActions | |
| event = Event( | |
| # 식별 정보 | |
| id="event_123", # 고유 ID | |
| invocation_id="inv_456", # 소속 invocation ID | |
| author="MyAgent", # 생성자 (에이전트명) | |
| # 내용 | |
| content=types.Content( # 실제 메시지/데이터 | |
| parts=[types.Part(text="안녕하세요!")] | |
| ), | |
| # 액션/변경사항 | |
| actions=EventActions( | |
| state_delta={"key": "value"}, # 상태 변경 | |
| artifact_delta={}, # 아티팩트 변경 | |
| escalate=False, # 루프 종료 신호 | |
| transfer_to_agent="OtherAgent", # 에이전트 전환 | |
| ), | |
| # 메타데이터 | |
| timestamp=1234567890.0, | |
| partial=False, # 스트리밍 중간 결과 여부 | |
| ) | |
| ``` | |
| ### Event 유형 | |
| | 유형 | 설명 | 식별 방법 | | |
| | ------------- | -------------------- | -------------------------------- | | |
| | 사용자 메시지 | 사용자 입력 | `event.content.role == "user"` | | |
| | 텍스트 응답 | 에이전트 텍스트 응답 | `event.content.parts[0].text` | | |
| | 도구 호출 | LLM이 도구 호출 요청 | `event.get_function_calls()` | | |
| | 도구 결과 | 도구 실행 결과 | `event.get_function_responses()` | | |
| | 상태 변경 | State 업데이트 | `event.actions.state_delta` | | |
| | 스트리밍 청크 | 부분 응답 | `event.partial == True` | | |
| ### EventActions | |
| 이벤트에 연결된 **부수 효과(side effects)**를 정의합니다: | |
| ```python | |
| from google.adk.events import EventActions | |
| actions = EventActions( | |
| # 상태 변경 | |
| state_delta={"new_key": "new_value"}, | |
| # 루프 종료 신호 (LoopAgent에서 사용) | |
| escalate=True, | |
| # 에이전트 전환 (LLM Transfer) | |
| transfer_to_agent="TargetAgentName", | |
| # 아티팩트(파일) 변경 | |
| artifact_delta={"file.txt": "content"}, | |
| ) | |
| ``` | |
| ### Event 처리 흐름 | |
| ``` | |
| ┌──────────────────────────────────────────────────────────────┐ | |
| │ Event 생성 및 처리 흐름 │ | |
| └──────────────────────────────────────────────────────────────┘ | |
| 1. Event 생성 | |
| Agent/Tool/Callback → yield Event(...) | |
| 2. Runner 수신 | |
| Runner가 이벤트를 받음 | |
| 3. SessionService 처리 | |
| - state_delta를 session.state에 병합 | |
| - artifact_delta 처리 | |
| - events 히스토리에 추가 | |
| 4. 클라이언트 전달 | |
| - UI/API로 이벤트 스트리밍 | |
| ``` | |
| --- | |
| ## 5. Context (컨텍스트) | |
| ### 개념 | |
| **Context**는 현재 실행 상황에 대한 **모든 정보를 담은 컨테이너**입니다. | |
| 에이전트, 도구, 콜백이 실행될 때 필요한 모든 정보에 접근할 수 있게 합니다. | |
| ### Context 유형 | |
| #### 1. InvocationContext (호출 컨텍스트) | |
| 가장 상위 레벨의 컨텍스트로, 하나의 사용자 요청 처리 전체를 관장합니다. | |
| ```python | |
| from google.adk.agents import BaseAgent | |
| from google.adk.agents.invocation_context import InvocationContext | |
| class MyAgent(BaseAgent): | |
| async def _run_async_impl(self, ctx: InvocationContext): | |
| # 세션 정보 접근 | |
| session_id = ctx.session.id | |
| user_id = ctx.session.user_id | |
| # 상태 접근 | |
| state_value = ctx.session.state.get("key") | |
| # 서비스 접근 | |
| memory_service = ctx.memory_service | |
| artifact_service = ctx.artifact_service | |
| # 현재 에이전트 정보 | |
| agent_name = ctx.agent.name | |
| # 호출 제어 | |
| ctx.end_invocation = True # 호출 종료 신호 | |
| yield Event(author=self.name, content=...) | |
| ``` | |
| #### 2. CallbackContext (콜백 컨텍스트) | |
| 콜백 함수에서 사용되며, InvocationContext의 서브셋입니다. | |
| ```python | |
| from google.adk.agents.callback_context import CallbackContext | |
| def my_before_agent_callback(ctx: CallbackContext): | |
| # 에이전트 정보 | |
| agent_name = ctx.agent_name | |
| invocation_id = ctx.invocation_id | |
| # 상태 읽기/쓰기 | |
| ctx.state["processed"] = True | |
| # 사용자 입력 확인 | |
| user_content = ctx.user_content | |
| return None # 계속 진행 (또는 Content 반환 시 에이전트 건너뜀) | |
| ``` | |
| #### 3. ToolContext (도구 컨텍스트) | |
| 도구(Tool) 함수에서 사용되며, 도구 실행에 필요한 추가 기능을 제공합니다. | |
| ```python | |
| from google.adk.tools import ToolContext | |
| def my_tool(context: ToolContext, query: str) -> dict: | |
| # 상태 접근 (CallbackContext와 동일) | |
| context.state["last_query"] = query | |
| # 도구 전용 기능 | |
| function_call_id = context.function_call_id # 현재 함수 호출 ID | |
| # 메모리 검색 | |
| results = await context.search_memory("검색어") | |
| # 아티팩트 목록 | |
| artifacts = context.list_artifacts() | |
| # 인증 요청 (OAuth 등) | |
| credential = await context.get_auth_response(auth_config) | |
| return {"result": "success"} | |
| ``` | |
| ### Context 계층 구조 | |
| ``` | |
| InvocationContext (최상위) | |
| ├── session | |
| │ ├── state | |
| │ └── events | |
| ├── agent (현재 에이전트) | |
| ├── services (memory, artifact, session) | |
| └── invocation_id | |
| ▼ 파생 | |
| CallbackContext (콜백용) | |
| ├── state (읽기/쓰기) | |
| ├── agent_name | |
| ├── user_content | |
| └── invocation_id | |
| ▼ 확장 | |
| ToolContext (도구용) | |
| ├── (CallbackContext의 모든 것) | |
| ├── function_call_id | |
| ├── search_memory() | |
| ├── list_artifacts() | |
| └── request_credential() | |
| ``` | |
| --- | |
| ## 6. Runner (러너) | |
| ### 개념 | |
| **Runner**는 에이전트 실행의 **중앙 오케스트레이터**입니다. | |
| 사용자 입력을 받아 에이전트를 실행하고, 이벤트를 처리하며, 서비스들을 조율합니다. | |
| ### 기본 사용법 | |
| ```python | |
| from google.adk.runners import InMemoryRunner, Runner | |
| from google.adk.sessions import InMemorySessionService | |
| # 방법 1: InMemoryRunner (간단한 테스트용) | |
| runner = InMemoryRunner( | |
| agent=my_agent, | |
| app_name="my_app" | |
| ) | |
| # 방법 2: Runner (커스터마이징 필요 시) | |
| session_service = InMemorySessionService() | |
| runner = Runner( | |
| agent=my_agent, | |
| app_name="my_app", | |
| session_service=session_service, | |
| # memory_service=my_memory_service, # 옵션 | |
| # artifact_service=my_artifact_service, # 옵션 | |
| ) | |
| ``` | |
| ### 에이전트 실행 | |
| ```python | |
| # 비동기 실행 (일반적인 방법) | |
| async for event in runner.run_async( | |
| user_id="user_123", | |
| session_id="session_456", # 새 세션이면 자동 생성 | |
| new_message="안녕하세요!" | |
| ): | |
| if event.content and event.content.parts: | |
| for part in event.content.parts: | |
| if hasattr(part, 'text') and part.text: | |
| print(f"[{event.author}]: {part.text}") | |
| # 실시간 스트리밍 (음성/영상) | |
| async for event in runner.run_live(...): | |
| ... | |
| ``` | |
| ### Runner 실행 흐름 | |
| ``` | |
| ┌─────────────────────────────────────────────────────────────────┐ | |
| │ Runner.run_async() 흐름 │ | |
| └─────────────────────────────────────────────────────────────────┘ | |
| 1. 세션 준비 | |
| ┌─────────────────┐ | |
| │ get/create │ ← 세션 조회 또는 생성 | |
| │ Session │ | |
| └─────────────────┘ | |
| │ | |
| ▼ | |
| 2. 사용자 메시지를 이벤트로 추가 | |
| ┌─────────────────┐ | |
| │ append_event │ ← 사용자 입력을 세션 히스토리에 추가 | |
| │ (user message) │ | |
| └─────────────────┘ | |
| │ | |
| ▼ | |
| 3. InvocationContext 생성 | |
| ┌─────────────────┐ | |
| │ Create │ ← 실행에 필요한 모든 정보 준비 | |
| │ InvocationContext│ | |
| └─────────────────┘ | |
| │ | |
| ▼ | |
| 4. 에이전트 실행 루프 | |
| ┌─────────────────────────────────────────────┐ | |
| │ ┌─────────────────┐ │ | |
| │ │ Agent.run() │ ← 에이전트 실행 │ | |
| │ └─────────────────┘ │ | |
| │ │ │ | |
| │ ▼ │ | |
| │ ┌─────────────────┐ │ | |
| │ │ yield Event │ ← 이벤트 생성 │ | |
| │ └─────────────────┘ │ | |
| │ │ │ | |
| │ ▼ │ | |
| │ ┌─────────────────┐ │ | |
| │ │ Process Event │ ← 이벤트 처리 │ | |
| │ │ - state_delta │ (상태 업데이트 등) │ | |
| │ │ - append to │ │ | |
| │ │ session │ │ | |
| │ └─────────────────┘ │ | |
| │ │ │ | |
| │ ▼ │ | |
| │ ┌─────────────────┐ │ | |
| │ │ yield to caller │ ← 호출자에게 전달 │ | |
| │ └─────────────────┘ │ | |
| │ │ │ | |
| │ ▼ │ | |
| │ [더 많은 이벤트?] ──Yes──▶ (반복) │ | |
| │ │ │ | |
| │ No │ | |
| │ │ │ | |
| └──────────┼───────────────────────────────────┘ | |
| │ | |
| ▼ | |
| 5. 실행 완료 | |
| ``` | |
| ### Invocation vs Agent Call vs Step | |
| ``` | |
| Invocation (호출) | |
| │ 사용자 메시지 → 최종 응답까지의 전체 사이클 | |
| │ | |
| ├── Agent Call 1 (에이전트 호출) | |
| │ │ 하나의 에이전트가 실행되는 단위 | |
| │ │ | |
| │ ├── Step 1 (스텝) | |
| │ │ └── LLM 호출 1회 + 도구 실행 | |
| │ │ | |
| │ ├── Step 2 | |
| │ │ └── LLM 호출 1회 + 도구 실행 | |
| │ │ | |
| │ └── (transfer_to_agent 발생) | |
| │ | |
| ├── Agent Call 2 (다른 에이전트로 전환) | |
| │ │ | |
| │ ├── Step 1 | |
| │ │ └── LLM 호출 + 최종 응답 | |
| │ │ | |
| │ └── (완료) | |
| │ | |
| └── Invocation 종료 | |
| ``` | |
| --- | |
| ## 7. Memory (메모리) | |
| ### 개념 | |
| **Memory**는 **장기 기억 저장소**입니다. | |
| 세션이 종료된 후에도 유지되며, 여러 세션에 걸쳐 정보를 검색할 수 있습니다. | |
| ### Session State vs Memory 비교 | |
| | 특성 | Session State | Memory | | |
| | --------- | ------------------------ | --------------- | | |
| | 범위 | 현재 세션 내 | 여러 세션 걸쳐 | | |
| | 수명 | 세션 종료 시 (보통) 삭제 | 영구 저장 | | |
| | 접근 방식 | Key-Value 직접 접근 | 검색 쿼리 | | |
| | 용도 | 대화 중 임시 데이터 | 과거 정보 참조 | | |
| | 비유 | 대화 중 메모장 | 도서관 아카이브 | | |
| ### MemoryService 사용 | |
| ```python | |
| from google.adk.memory import InMemoryMemoryService | |
| # 메모리 서비스 생성 | |
| memory_service = InMemoryMemoryService() | |
| # 세션을 메모리에 저장 (대화 종료 후) | |
| await memory_service.add_session_to_memory(session) | |
| # 메모리 검색 | |
| results = await memory_service.search_memory( | |
| app_name="my_app", | |
| user_id="user_123", | |
| query="이전에 추천받은 음식점" | |
| ) | |
| for result in results.memories: | |
| print(result.content) | |
| ``` | |
| ### MemoryService 구현체 | |
| | 구현체 | 저장 방식 | 검색 방식 | 용도 | | |
| | --------------------------- | --------- | ----------- | ----------- | | |
| | `InMemoryMemoryService` | 메모리 | 키워드 매칭 | 개발/테스트 | | |
| | `VertexAIMemoryBankService` | Vertex AI | 시맨틱 검색 | 프로덕션 | | |
| ### 메모리 워크플로우 | |
| ``` | |
| ┌──────────────────────────────────────────────────────────────┐ | |
| │ Memory 워크플로우 │ | |
| └──────────────────────────────────────────────────────────────┘ | |
| 1. 세션 대화 진행 | |
| User ↔ Agent (Session에 Events 축적) | |
| 2. 세션 종료 시 메모리 저장 | |
| memory_service.add_session_to_memory(session) | |
| └── 세션의 주요 정보를 메모리에 저장 | |
| 3. 이후 세션에서 메모리 검색 | |
| results = memory_service.search_memory(query) | |
| └── 과거 대화에서 관련 정보 검색 | |
| 4. 검색 결과를 현재 대화에 활용 | |
| Agent가 검색 결과를 참고하여 응답 | |
| ``` | |
| ### 에이전트에서 메모리 사용 | |
| ```python | |
| from google.adk.tools import preload_memory_tool | |
| # 메모리 검색 도구 추가 | |
| agent = LlmAgent( | |
| name="MemoryAgent", | |
| instruction="사용자의 과거 대화를 참고하여 답변하세요.", | |
| tools=[preload_memory_tool.PreloadMemoryTool()] | |
| ) | |
| # 세션 종료 시 자동 저장 콜백 | |
| async def auto_save_to_memory(callback_context): | |
| await callback_context._invocation_context.memory_service.add_session_to_memory( | |
| callback_context._invocation_context.session | |
| ) | |
| agent = LlmAgent( | |
| name="AutoSaveAgent", | |
| after_agent_callback=auto_save_to_memory | |
| ) | |
| ``` | |
| --- | |
| ## 8. Callbacks (콜백) | |
| ### 개념 | |
| **Callback**은 에이전트 실행 중 **특정 시점에 개입**할 수 있는 훅(hook)입니다. | |
| 가드레일, 로깅, 수정 등 다양한 용도로 사용됩니다. | |
| ### 콜백 종류 및 실행 시점 | |
| ``` | |
| ┌─────────────────────────────────────────────────────────────────┐ | |
| │ 콜백 실행 시점 │ | |
| └─────────────────────────────────────────────────────────────────┘ | |
| 사용자 입력 | |
| │ | |
| ▼ | |
| ┌───────────────────────┐ | |
| │ before_agent_callback │ ← 에이전트 실행 전 | |
| └───────────────────────┘ | |
| │ | |
| ▼ | |
| ┌───────────────────────┐ | |
| │ before_model_callback │ ← LLM 호출 전 | |
| └───────────────────────┘ | |
| │ | |
| ▼ | |
| [LLM 호출] | |
| │ | |
| ▼ | |
| ┌───────────────────────┐ | |
| │ after_model_callback │ ← LLM 응답 후 | |
| └───────────────────────┘ | |
| │ | |
| ▼ | |
| ┌───────────────────────┐ | |
| │ before_tool_callback │ ← 도구 실행 전 | |
| └───────────────────────┘ | |
| │ | |
| ▼ | |
| [도구 실행] | |
| │ | |
| ▼ | |
| ┌───────────────────────┐ | |
| │ after_tool_callback │ ← 도구 실행 후 | |
| └───────────────────────┘ | |
| │ | |
| ▼ | |
| ┌───────────────────────┐ | |
| │ after_agent_callback │ ← 에이전트 실행 후 | |
| └───────────────────────┘ | |
| │ | |
| ▼ | |
| 최종 응답 | |
| ``` | |
| ### 콜백 정의 및 사용 | |
| #### before_agent_callback | |
| ```python | |
| from google.adk.agents import LlmAgent | |
| from google.adk.agents.callback_context import CallbackContext | |
| from google.genai import types | |
| def check_access(ctx: CallbackContext) -> types.Content | None: | |
| """에이전트 실행 전 접근 제어""" | |
| user_id = ctx.session.user_id | |
| # 차단할 사용자 체크 | |
| if user_id in BLOCKED_USERS: | |
| # Content 반환 시 에이전트 실행 건너뜀 | |
| return types.Content( | |
| parts=[types.Part(text="접근이 거부되었습니다.")] | |
| ) | |
| # None 반환 시 정상 진행 | |
| return None | |
| agent = LlmAgent( | |
| name="SecureAgent", | |
| before_agent_callback=check_access | |
| ) | |
| ``` | |
| #### before_model_callback (가드레일) | |
| ```python | |
| from google.adk.models import LlmRequest, LlmResponse | |
| def content_guardrail(ctx: CallbackContext, request: LlmRequest) -> LlmResponse | None: | |
| """LLM 호출 전 입력 검사""" | |
| user_text = ctx.user_content.parts[0].text | |
| # 금지어 체크 | |
| if any(word in user_text for word in FORBIDDEN_WORDS): | |
| return LlmResponse( | |
| content=types.Content( | |
| parts=[types.Part(text="부적절한 내용이 감지되었습니다.")] | |
| ) | |
| ) | |
| # 프롬프트 수정 | |
| # request.contents를 수정하여 시스템 지시 추가 가능 | |
| return None # 정상 진행 | |
| agent = LlmAgent( | |
| name="GuardedAgent", | |
| before_model_callback=content_guardrail | |
| ) | |
| ``` | |
| #### after_model_callback | |
| ```python | |
| def post_process_response(ctx: CallbackContext, response: LlmResponse) -> LlmResponse | None: | |
| """LLM 응답 후처리""" | |
| # 응답 로깅 | |
| print(f"LLM Response: {response.content}") | |
| # 응답 수정 (필요시) | |
| # return modified_response | |
| return None # 원본 응답 유지 | |
| agent = LlmAgent( | |
| name="LoggingAgent", | |
| after_model_callback=post_process_response | |
| ) | |
| ``` | |
| #### before_tool_callback / after_tool_callback | |
| ```python | |
| def tool_guardrail(ctx: ToolContext, tool_name: str, args: dict) -> dict | None: | |
| """도구 실행 전 검사""" | |
| if tool_name == "delete_file" and not ctx.state.get("admin"): | |
| return {"error": "권한이 없습니다."} # 도구 실행 건너뜀 | |
| return None | |
| def log_tool_result(ctx: ToolContext, tool_name: str, result: dict) -> dict | None: | |
| """도구 실행 후 로깅""" | |
| print(f"Tool {tool_name} returned: {result}") | |
| return None # 결과 유지 | |
| agent = LlmAgent( | |
| name="ToolAuditAgent", | |
| before_tool_callback=tool_guardrail, | |
| after_tool_callback=log_tool_result | |
| ) | |
| ``` | |
| ### 콜백 반환값의 의미 | |
| | 콜백 | 반환값 | 효과 | | |
| | ------------ | ------------- | ------------------------------------ | | |
| | before_agent | `None` | 에이전트 정상 실행 | | |
| | before_agent | `Content` | 에이전트 건너뜀, 반환된 Content 사용 | | |
| | before_model | `None` | LLM 정상 호출 | | |
| | before_model | `LlmResponse` | LLM 호출 건너뜀, 반환된 응답 사용 | | |
| | before_tool | `None` | 도구 정상 실행 | | |
| | before_tool | `dict` | 도구 실행 건너뜀, 반환된 결과 사용 | | |
| --- | |
| ## 9. Tools (도구) | |
| ### 개념 | |
| **Tool**은 에이전트에게 **외부 기능을 제공**하는 컴포넌트입니다. | |
| API 호출, 계산, 데이터베이스 조회 등을 수행할 수 있습니다. | |
| ### Tool 유형 | |
| #### 1. FunctionTool (함수 도구) | |
| Python 함수를 도구로 변환합니다. | |
| ```python | |
| from google.adk.tools import FunctionTool | |
| def get_weather(city: str, unit: str = "celsius") -> dict: | |
| """ | |
| 특정 도시의 날씨를 조회합니다. | |
| Args: | |
| city: 조회할 도시 이름 | |
| unit: 온도 단위 (celsius 또는 fahrenheit) | |
| Returns: | |
| 날씨 정보 딕셔너리 | |
| """ | |
| # 실제 API 호출 또는 시뮬레이션 | |
| return {"city": city, "temp": 20, "unit": unit} | |
| # 함수를 도구로 변환 | |
| weather_tool = FunctionTool(func=get_weather) | |
| # 에이전트에 도구 추가 | |
| agent = LlmAgent( | |
| name="WeatherAgent", | |
| tools=[weather_tool] | |
| ) | |
| ``` | |
| #### 2. AgentTool (에이전트를 도구로) | |
| 다른 에이전트를 도구처럼 호출할 수 있습니다. | |
| ```python | |
| from google.adk.tools import agent_tool | |
| # 전문 에이전트 정의 | |
| translator = LlmAgent( | |
| name="Translator", | |
| description="텍스트를 번역합니다." | |
| ) | |
| # 에이전트를 도구로 래핑 | |
| translator_tool = agent_tool.AgentTool(agent=translator) | |
| # 다른 에이전트에서 도구로 사용 | |
| main_agent = LlmAgent( | |
| name="MainAgent", | |
| tools=[translator_tool] | |
| ) | |
| ``` | |
| ### ToolContext 활용 | |
| 도구 함수 내에서 Context에 접근하여 다양한 기능을 사용할 수 있습니다. | |
| ```python | |
| from google.adk.tools import ToolContext | |
| def advanced_tool(context: ToolContext, query: str) -> dict: | |
| # 1. 상태 읽기/쓰기 | |
| context.state["last_query"] = query | |
| previous = context.state.get("history", []) | |
| # 2. 메모리 검색 | |
| memory_results = await context.search_memory(query) | |
| # 3. 아티팩트 목록 조회 | |
| artifacts = context.list_artifacts() | |
| # 4. 인증 처리 | |
| if needs_auth: | |
| cred = await context.get_auth_response(oauth_config) | |
| return {"result": "processed"} | |
| ``` | |
| ### 도구 실행 흐름 | |
| ``` | |
| ┌──────────────────────────────────────────────────────────────┐ | |
| │ 도구 실행 흐름 │ | |
| └──────────────────────────────────────────────────────────────┘ | |
| 1. LLM이 도구 호출 결정 | |
| LLM Response: FunctionCall(name="get_weather", args={"city": "서울"}) | |
| 2. ADK가 도구 찾기 | |
| tools 목록에서 "get_weather" 찾음 | |
| 3. before_tool_callback 실행 (있으면) | |
| 가드레일, 로깅 등 | |
| 4. 도구 함수 실행 | |
| result = get_weather(city="서울") | |
| 5. after_tool_callback 실행 (있으면) | |
| 결과 후처리, 로깅 등 | |
| 6. FunctionResponse 이벤트 생성 | |
| Event(content=FunctionResponse(name="get_weather", response=result)) | |
| 7. LLM에게 결과 전달 | |
| LLM이 결과를 보고 다음 응답 생성 | |
| ``` | |
| --- | |
| ## 10. 핵심 개념 간의 관계 | |
| ### 전체 관계도 | |
| ``` | |
| ┌─────────────────────────────────────────────────────────────────────────┐ | |
| │ ADK 핵심 개념 관계도 │ | |
| └─────────────────────────────────────────────────────────────────────────┘ | |
| ┌─────────────────────┐ | |
| │ Runner │ | |
| │ (오케스트레이터) │ | |
| └─────────────────────┘ | |
| │ | |
| ┌───────────────┼───────────────┐ | |
| ▼ ▼ ▼ | |
| ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ | |
| │ SessionSvc │ │ MemorySvc │ │ ArtifactSvc │ | |
| └─────────────┘ └─────────────┘ └─────────────┘ | |
| │ │ | |
| ▼ │ | |
| ┌─────────────────┐ │ | |
| │ Session │ │ | |
| │ ┌───────────┐ │ │ | |
| │ │ State │◀─┼──────┼──── 에이전트가 읽고/씀 | |
| │ └───────────┘ │ │ | |
| │ ┌───────────┐ │ │ | |
| │ │ Events │ │ │ | |
| │ └───────────┘ │ │ | |
| └─────────────────┘ │ | |
| │ │ | |
| ▼ ▼ | |
| ┌─────────────────────────────────────────┐ | |
| │ InvocationContext │ | |
| │ ┌─────────────────────────────────┐ │ | |
| │ │ session, state, services, agent │ │ | |
| │ └─────────────────────────────────┘ │ | |
| └─────────────────────────────────────────┘ | |
| │ | |
| ┌─────────────┼─────────────┐ | |
| ▼ ▼ ▼ | |
| ┌────────────┐ ┌──────────┐ ┌──────────┐ | |
| │ Callbacks │ │ Agent │ │ Tools │ | |
| │ │ │ │ │ │ | |
| │ Callback │ │ │ │ Tool │ | |
| │ Context │ │ │ │ Context │ | |
| └────────────┘ └──────────┘ └──────────┘ | |
| │ │ │ | |
| └─────────────┼─────────────┘ | |
| ▼ | |
| ┌────────────┐ | |
| │ Event │ | |
| │ ┌──────┐ │ | |
| │ │Actions│ ──▶ state_delta | |
| │ └──────┘ │ escalate | |
| └────────────┘ transfer | |
| ``` | |
| ### 데이터 흐름 요약 | |
| ``` | |
| 1. 사용자 입력 | |
| └──▶ Runner.run_async(message) | |
| 2. 세션 준비 | |
| └──▶ SessionService.get_session() → Session | |
| 3. 컨텍스트 생성 | |
| └──▶ InvocationContext(session, services, ...) | |
| 4. 콜백 실행 | |
| └──▶ before_agent_callback(CallbackContext) | |
| 5. 에이전트 실행 | |
| └──▶ Agent.run(InvocationContext) | |
| │ | |
| ├──▶ before_model_callback | |
| ├──▶ LLM 호출 | |
| ├──▶ after_model_callback | |
| │ | |
| ├──▶ [도구 호출 시] | |
| │ ├──▶ before_tool_callback | |
| │ ├──▶ Tool 실행(ToolContext) | |
| │ └──▶ after_tool_callback | |
| │ | |
| └──▶ yield Event | |
| 6. 이벤트 처리 | |
| └──▶ SessionService.append_event(event) | |
| └──▶ state_delta 적용 | |
| └──▶ events 히스토리 추가 | |
| 7. 콜백 실행 | |
| └──▶ after_agent_callback(CallbackContext) | |
| 8. 결과 반환 | |
| └──▶ yield event to caller | |
| ``` | |
| ### 핵심 포인트 정리 | |
| | 개념 | 역할 | 비유 | | |
| | ------------ | -------------- | ------------------- | | |
| | **Session** | 대화 컨테이너 | 전화 통화 한 건 | | |
| | **State** | 대화 중 메모 | 메모장/스크래치패드 | | |
| | **Event** | 활동 기록 단위 | 대화 기록의 한 줄 | | |
| | **Context** | 실행 정보 묶음 | 업무 문맥/배경 정보 | | |
| | **Runner** | 실행 조율자 | 지휘자 | | |
| | **Memory** | 장기 기억 | 도서관 아카이브 | | |
| | **Callback** | 실행 중 개입점 | 체크포인트/관문 | | |
| | **Tool** | 외부 기능 | 도구 상자 | | |
| --- | |
| ## 부록: 자주 묻는 질문 (FAQ) | |
| ### Q1: State와 Memory의 차이는? | |
| - **State**: 현재 세션 내에서만 유효, 키-값 직접 접근 | |
| - **Memory**: 여러 세션에 걸쳐 유지, 검색 쿼리로 접근 | |
| ### Q2: output_key가 없으면 어떻게 되나요? | |
| 에이전트의 응답이 state에 자동 저장되지 않습니다. | |
| 다음 에이전트에서 이전 응답을 참조하려면 output_key를 설정하거나, | |
| 직접 콜백/도구에서 state를 업데이트해야 합니다. | |
| ### Q3: InMemorySessionService는 프로덕션에서 쓸 수 있나요? | |
| 권장하지 않습니다. 앱 재시작 시 모든 데이터가 손실됩니다. | |
| 프로덕션에서는 Database-backed 또는 Cloud-based 구현체를 사용하세요. | |
| ### Q4: 콜백에서 Content를 반환하면 무슨 일이 일어나나요? | |
| before_agent_callback에서 Content를 반환하면 에이전트 실행이 건너뛰어지고, | |
| 반환된 Content가 에이전트의 응답으로 사용됩니다. | |
| 이를 통해 조건부 단축(short-circuit)을 구현할 수 있습니다. | |
| ### Q5: LoopAgent의 escalate는 어떻게 동작하나요? | |
| sub_agent가 Event를 생성할 때 `actions=EventActions(escalate=True)`를 설정하면, | |
| LoopAgent가 이를 감지하고 즉시 루프를 종료합니다. | |
| max_iterations에 도달하기 전에 조건부로 종료할 때 사용합니다. | |
| --- | |
| ## 참고 자료 | |
| - [ADK 공식 문서](https://google.github.io/adk-docs/) | |
| - [ADK GitHub (Python)](https://github.com/google/adk-python) | |
| - [Vertex AI Agent Engine](https://cloud.google.com/vertex-ai/docs/agents) | |