ADK로 처음 만든 멀티에이전트가 이 글을 썼습니다
Google I/O 2026에서 ADK를 보고 왔다. 두 주 뒤에 직접 만들어봤다.
1. 배경: 발표 준비하다가 직접 써보기로 했다
Google I/O 2026에 다녀온 뒤 사내 개발자 대상 발표를 준비하면서 ADK를 열심히 설명했다.
설명하다 보니 한 가지가 마음에 걸렸다.
"멀티에이전트가 이렇게 쉬워졌다"고 말하면서 정작 나는 한 번도 ADK를 직접 실행해본 적이 없었다.
그래서 만들어봤다.
주제는 단순하게 잡았다. 블로그 포스트를 자동으로 써서 Ghost에 올려주는 3에이전트 파이프라인. 메타하게도, 이 글 자체가 그 파이프라인의 첫 번째 결과물이다.
2. 에이전트 구조: 팀장 + 기획자 + 작가
ADK에서 멀티에이전트는 이런 구조로 만든다.
orchestrator (감독, gemini-2.5-flash)
├── researcher_agent (기획, gemini-2.5-flash)
└── writer_agent (작성, gemini-2.5-pro)
오케스트레이터는 일을 받아서 sub_agents에게 나눠준다. 스스로 많은 일을 하지 않는다. 좋은 팀장처럼.
researcher_agent는 주제와 컨텍스트를 받아 핵심 포인트·글 구조·강조할 수치를 정리한다. 빠르고 저렴한 Flash 모델로 충분하다.
writer_agent는 researcher의 요약을 받아 완성된 한국어 블로그 포스트를 쓴다. 여기만 품질이 중요해서 Pro 모델을 쓴다.
코드로 보면 이렇다.
from google.adk.agents import Agent
researcher = Agent(
name="researcher_agent",
model="gemini-2.5-flash",
instruction="주제를 분석해 핵심 포인트와 글 구조를 정리하라.",
)
writer = Agent(
name="writer_agent",
model="gemini-2.5-pro",
instruction="한국어 블로그 포스트를 번호 체계로 작성하라.",
)
orchestrator = Agent(
name="orchestrator",
model="gemini-2.5-flash",
instruction="""
1. researcher_agent에게 주제 분석 위임
2. 결과를 writer_agent에게 넘겨 포스트 작성
3. writer_agent 결과를 최종 출력
""",
sub_agents=[researcher, writer],
)
sub_agents 한 줄이 전부다. 오케스트레이터는 두 에이전트를 마치 도구처럼 호출한다. 내가 직접 researcher → writer 순서를 코딩할 필요가 없다. 오케스트레이터의 LLM이 instruction을 보고 스스로 판단한다.
3. 실행 방법: async Runner 패턴
ADK 2.x에서 에이전트를 실행하는 방법은 Runner + asyncio다.
import asyncio
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
from google.genai import types
async def run_pipeline(topic: str, context: str) -> str:
session_service = InMemorySessionService()
session = await session_service.create_session(
app_name="blog_writer", user_id="user"
)
runner = Runner(
agent=orchestrator,
app_name="blog_writer",
session_service=session_service,
)
message = types.Content(
role="user",
parts=[types.Part(text=f"주제: {topic}\n\n컨텍스트:\n{context}")]
)
result = ""
async for event in runner.run_async(
user_id="user", session_id=session.id, new_message=message
):
if event.is_final_response():
result = event.content.parts[0].text
return result
asyncio.run(run_pipeline("ADK 첫 실습 후기", context_text))
실행하면 터미널에서 에이전트 전환이 보인다.
[1/3] 오케스트레이터 실행 중...
→ researcher_agent 처리 중...
→ writer_agent 처리 중...
[2/3] 로컬 저장 완료: output/ADK-첫-실습-후기.md
[3/3] Draft 생성 완료!
확인 URL: https://your-blog.ghost.io/ghost/#/editor/post/abc123
researcher_agent → writer_agent 순서로 자동 위임된다. 이게 ADK가 약속한 것의 실제 모습이다.
4. Ghost 연동: JWT 인증 + Draft 자동 업로드
Ghost Admin API는 JWT로 인증한다. Admin API 키 형식은 {id}:{secret}.
import jwt, time, requests
def create_token(admin_api_key: str) -> str:
key_id, secret = admin_api_key.split(":", 1)
iat = int(time.time())
return jwt.encode(
{"iat": iat, "exp": iat + 300, "aud": "/admin/"},
bytes.fromhex(secret),
algorithm="HS256",
headers={"kid": key_id},
)
def publish_draft(title: str, html: str) -> dict:
token = create_token(os.environ["GHOST_ADMIN_API_KEY"])
resp = requests.post(
f"{os.environ['GHOST_API_URL']}/ghost/api/admin/posts/",
json={"posts": [{"title": title, "html": html, "status": "draft"}]},
headers={"Authorization": f"Ghost {token}"},
)
resp.raise_for_status()
return resp.json()["posts"][0]
Ghost 관리자에 Draft가 올라오면 검토 후 수동 발행한다. 자동화는 여기까지, 판단은 사람이.
5. 직접 써보면서 확인된 것
확인 1. 오케스트레이터는 생각보다 얇다.
orchestrator instruction을 너무 자세하게 쓸 필요가 없었다. 순서를 1-2-3으로 적어주면 LLM이 알아서 sub_agents를 호출한다. 처음에는 더 많은 로직이 필요할 줄 알았다.
확인 2. 모델 분리가 비용에 바로 영향을 준다.
researcher를 Pro로 돌렸더니 비용이 약 3배 올라갔다. Flash로 바꿔도 리서치 품질 차이가 없었다. 발표에서 "$2.07 → $0.82" 수치를 설명했는데, 실제로 모델 선택이 그만큼 중요하다는 걸 직접 느꼈다.
확인 3. Drift는 실제로 빠르게 온다.
테스트 중에 orchestrator instruction이 길어지자 에이전트가 writer 단계를 건너뛰고 바로 결과를 출력하는 현상이 생겼다. AI DevSummit에서 들은 "반복 8회 이후 컨텍스트 손실"이 instruction 길이에서도 발생했다. Instruction을 짧고 명확하게 유지하는 게 이래서 중요하다.
6. 아직 안 해본 것
이번 데모는 의도적으로 최소한으로 만들었다.
- 평가 없음: writer_agent 결과가 잘 됐는지 LLM-as-judge로 채점하는 부분이 없다. 발표에서 강조한 것과 역설적이다.
- 재시도 없음: writer가 품질 미달 결과를 내도 그냥 Ghost에 올라간다. Fallback 설계가 빠져 있다.
- 비용 상한 없음:
budget_tokens미설정. 루프가 길어지면 비용이 올라간다.
만들면서 더 확실해졌다. ADK로 만드는 건 쉽다. 잘 만드는 건 별개의 문제다.
마치며
Google I/O에서 ADK 코드를 처음 보고 "이게 말이 되나?" 싶었다. 직접 써보니 편하긴 정말 편하다. 그리고 발표에서 말했던 것들이 하나씩 실체로 느껴졌다. 평가가 없으면 모른다는 것. Drift가 instruction 길이에서도 온다는 것. 모델 선택이 비용을 즉시 바꾼다는 것.
이해는 만들어봐야 온다.
소스코드
~/dev/adk-ghost-demo/ — 로컬에 있음
adk-ghost-demo/
├── main.py # 오케스트레이터 + CLI
├── context.txt # 배경 컨텍스트 (수정해서 재사용 가능)
├── agents/
│ ├── researcher.py # Flash 모델, 주제 분석
│ └── writer.py # Pro 모델, 블로그 작성
└── tools/
└── ghost_publisher.py # Ghost Admin API JWT 인증
설치 및 실행:
cd ~/dev/adk-ghost-demo
pip install -r requirements.txt
cp .env.example .env # GOOGLE_API_KEY, GHOST_API_URL, GHOST_ADMIN_API_KEY 입력
# Ghost 연동 단독 테스트 (Gemini 키 없이 가능)
python tools/ghost_publisher.py
# 전체 파이프라인 (Gemini 키 필요)
python main.py --topic "내 주제"
Member discussion