드디어 온 ADK for Go 2.0: 왜 지금 주목해야 할까?
현실에서 에이전트 애플리케이션은 단순한 프롬프트 하나로 끝나지 않습니다. 분류, 분기, 병렬 실행, 인간 승인 요청, 재시도, 반복 등 복잡한 오케스트레이션이 필요하죠. 이 모든 것을 임시방편 제어 흐름으로 처리하면 유지보수는 금세 지옥이 됩니다.
ADK for Go 1.0은 강력한 타입 시스템과 iter.Seq2 이벤트 스트림, 그리고 기존 Go 서비스에 자연스럽게 녹아드는 런타임으로 큰 호평을 받았습니다. 그리고 이제 2.0이 등장했습니다.
핵심은 '그래프'입니다.
ADK 2.0은 멀티 에이전트 애플리케이션을 구성하는 새로운 일급 시민(first-class) 방식으로 그래프 기반 워크플로우 엔진을 도입했습니다. 여기에 Human-in-the-loop(HITL), 동적 오케스트레이션, LLM 에이전트 모드까지 더해져 단일 에이전트와 전체 그래프가 동일한 실행 모델 위에서 돌아갑니다.
이미 Python ADK 2.0을 접해보셨다면 같은 방향성이라고 보시면 됩니다. 단, Go 개발자에게 'Go스러운' API로 설계되었다는 점이 가장 큰 차별점입니다.
이 글에서는 ADK for Go 2.0의 핵심 개념과 코드 예제를 통해 실제 프로젝트에 어떻게 적용할 수 있을지 살펴보겠습니다.

그래프 기반 워크플로우: 코드로 보는 핵심
ADK 2.0의 가장 큰 변화는 애플리케이션의 형태를 노드(Node)와 엣지(Edge)로 구성된 그래프로 표현하고, 스케줄러가 이를 동시에 실행하고 상태를 유지하며, 인간의 개입을 위해 일시 중지하고, 나중에 재개할 수 있도록 한 점입니다.
기본 체인: 가장 단순한 그래프
package main
import (
"context"
"fmt"
"google.golang.org/adk/v2/workflow"
"google.golang.org/adk/v2/agent"
)
// 대문자로 변환하는 노드
func upperFn(ctx agent.Context, in string) (string, error) {
return strings.ToUpper(in), nil
}
// 문자열 뒤에 접미사를 붙이는 노드
func suffixFn(ctx agent.Context, in string) (string, error) {
return in + " - 완료!", nil
}
func main() {
upper := workflow.NewFunctionNode("upper", upperFn, workflow.NodeConfig{})
suffix := workflow.NewFunctionNode("suffix", suffixFn, workflow.NodeConfig{})
// 시작 -> upper -> suffix 순서로 연결
edges := workflow.Chain(workflow.Start, upper, suffix)
wf, err := workflowagent.New(workflowagent.Config{
Name: "simple_sequence_workflow",
Edges: edges,
})
if err != nil {
panic(err)
}
// wf는 그냥 agent.Agent 인터페이스입니다.
// 기존 Runner, Launcher, Console에서 동일하게 실행 가능!
fmt.Println("워크플로우 생성 완료")
}
중요한 점: wf는 agent.Agent 인터페이스를 구현합니다. 따라서 기존에 사용하던 Runner, Launcher, Console에서 특별한 변경 없이 바로 실행할 수 있습니다. 그래프 자체가 하나의 에이전트인 셈이죠.
라우팅: 조건부 분기
엣지는 라우팅 조건을 가질 수 있습니다. 노드가 라우팅 값을 방출하면 일치하는 엣지가 실행됩니다. 이 단순한 아이디어로 모든 제어 흐름을 표현할 수 있습니다.
b := workflow.NewEdgeBuilder()
b.AddRoutes(router, map[string]workflow.Node{
"question": answerNode,
"statement": commentNode,
"exclamation": reactNode,
})
b.AddFanOut(planner, researchA, researchB, researchC) // 병렬 분기
b.AddFanIn(join, researchA, researchB, researchC) // 결과 수집
- 순차 체인, 조건부 라우터, Fan-out/Fan-in, 중첩 서브그래프, 심지어 루프(완료된 노드를 재실행 가능)까지 모두 엣지와 라우트로 구현됩니다.
- 기본 라우트로
StringRoute,IntRoute,BoolRoute,MultiRoute,Default(그 외 모든 경우)를 제공합니다.
LLM을 라우터로 활용: 가장 유용한 패턴
// 사용자 메시지를 분류하는 LLM 에이전트
// -> 질문이면 answerNode, 감탄이면 reactNode, 진술이면 commentNode로 라우팅
모델이 결정을 내리고, 그래프가 그 결정을 **신뢰할 수 있고(Reliable), 관찰 가능하며(Observable), 재개 가능(Resumable)**하게 만듭니다. 이 패턴은 examples/workflow/routing/llm/에서 확인할 수 있습니다.
동적 노드(Dynamic Node): 실행 순서를 런타임에 결정
greeter := workflow.NewDynamicNode("greeter_workflow",
func(nc agent.Context, in string, emit func(*session.Event) error) (string, error) {
return workflow.RunNode[string](nc, greeterNode, in)
},
workflow.NodeConfig{},
)
루프, 조건문, 동적 리스트에 대한 Fan-out 등 익숙한 Go 코드로 오케스트레이션을 표현할 수 있습니다. WithRunID, WithUseSubBranch, WithUseAsOutput, WithIsolationScope 옵션으로 자식 노드의 정체성, 히스토리 격리, 출력 위임을 정밀하게 제어할 수 있습니다.
Human-in-the-loop(HITL): 인간의 승인을 기다리는 워크플로우
event := workflow.NewRequestInputEvent(ctx, session.RequestInput{
InterruptID: "approve_refund",
Message: "200달러 환불을 승인하시겠습니까? (yes/no)",
ResponseSchema: schema,
})
// 이벤트를 방출하면 노드는 "waiting" 상태로 전환
인간이 나중에 응답하면 워크플로우가 재개됩니다. 실행 상태는 세션에 저장되며, 프로세스 재시작 후에도 재개 가능합니다. 심지어 Python ADK와 인터럽트 형식을 공유하므로 다른 런타임 간에도 재개가 가능합니다.
재시도 정책: 내장된 지수 백오프
cfg := workflow.NodeConfig{
RetryConfig: workflow.DefaultRetryConfig(),
}
// 5회 시도, 초기 지연 1초, 최대 60초, 2배 백오프, 전체 지터
Timeout, WithMaxConcurrency(n)으로 그래프 전체 동시성을 제한하고, 격리된 병렬 브랜치를 구성할 수 있습니다. 스케줄러가 고루틴, 채널, 백프레셔, 취소를 모두 처리합니다.

LLM 에이전트 모드와 통합된 런타임
ADK 2.0은 LLM 에이전트를 위한 모드(Chat, Task, SingleTurn)를 도입했습니다. 코디네이터는 사용자와 채팅하면서 서브 에이전트는 조용히 Task를 완료하거나 Single-shot으로 실행됩니다. 각 에이전트의 역할에 따라 적절한 헬퍼 도구(finish_task, single_turn, task)가 자동으로 설치됩니다.
가장 큰 변화: 이제 Runner가 일반 LlmAgent를 워크플로우와 동일한 노드 런타임으로 구동합니다. 결과적으로 단일 에이전트 앱과 전체 그래프가 하나의 실행 모델을 공유하고, HITL이 일반 LLM 에이전트에서도 동작합니다(워크플로우 내부뿐만 아니라).
API 단순화: ToolContext와 CallbackContext가 하나로
ToolContext와CallbackContext가 사라지고agent.Context로 통합되었습니다. 도구, 콜백, 워크플로우 노드 모두agent.Context를 직접 받습니다.- 노드/에이전트 실행이 하나의 일관된 텔레메트리 스팬 트리로 표시되어 그래프가 무엇을 했는지 정확히 추적할 수 있습니다.
마이그레이션 가이드 (핵심만):
// 변경 전: func(ctx agent.InvocationContext, in string) (string, error)
// 변경 후: func(ctx agent.Context, in string) (string, error)
InvocationContext를 임베딩하고 있으므로 기존 메서드는 모두 사용 가능합니다. agent/context_mock.go의 StrictContextMock을 테스트 더블로 사용할 수 있습니다.
빠르게 체험해보기
go run ./examples/workflow/basic/
go run ./examples/workflow/routing/llm/ # LLM-as-router
go run ./examples/workflow/dynamic/hitl/ # 동적 + HITL
go run ./examples/workflow/hitl_rerun/ # 재진입 재개 HITL
go run ./examples/workflow/complex/ # 대규모 멀티 그래프

한국 개발 생태계에서의 적용 맥락
한국에서는 SI/금융권을 중심으로 **장기 실행 프로세스(Long-running Process)**와 인간 승인 워크플로우가 매우 중요한 도메인입니다.
- 예시: 보험 청구 처리, 금융 거래 승인, 대출 심사 등.
- ADK 2.0의 HITL과 durable resume 기능은 이러한 요구사항과 완벽히 일치합니다. 특히 프로세스 재시작 후에도 상태가 유지되는 점은 금융권에서 매우 중요한 요소입니다.
- 다만, 국내 클라우드 환경에서 Go 런타임을 운영할 때는 메모리 프로파일링과 고루틴 누수에 항상 주의해야 합니다. ADK 2.0의 그래프 스케줄러가 많은 고루틴을 생성할 수 있으므로,
WithMaxConcurrency로 적절히 제한하는 것이 좋습니다.
이 기술의 한계 또는 주의사항
- 학습 곡선: 그래프 개념 자체는 직관적이지만, 복잡한 워크플로우를 설계할 때는 노드 간 데이터 흐름을 명확히 정의해야 합니다. 특히 동적 노드를 남용하면 오히려 유지보수가 어려워질 수 있습니다.
- 테스트 복잡성: 그래프 전체를 통합 테스트하기보다는 개별 노드 단위로 집중 테스트하는 전략이 효과적입니다.
session.Event의 새로운 필드(IsolationScope, Output, Routes 등)에 주의하세요. - Python ADK와의 차이: Go 2.0은 Python 2.0과 같은 방향성을 공유하지만, Go 고유의 정적 타입 시스템과
iter.Seq2를 활용합니다. Python에서처럼 동적 타입 변환을 기대하면 안 됩니다.
다음 단계 학습 방향
- 공식 예제 실행: 위에서 소개한
go run ./examples/workflow/명령어로 기본 패턴을 직접 실행해보세요. - 마이그레이션 가이드 숙지: ADK Go 2.0 마이그레이션 가이드를 참고하여 1.0 코드를 2.0으로 전환하는 연습을 해보세요.
- 복합 패턴 설계: 여러분의 실제 업무 도메인에서 하나의 워크플로우를 그래프로 설계해보고, HITL이 필요한 지점을 식별해보세요.
함께 보면 좋은 글