ADK Go 2.0: 그래프 기반 워크플로우 엔진으로 안정적인 멀티 에이전트 애플리케이션을 만들어 보세요!
요약
ADK Go 2.0이 새로운 그래프 기반 워크플로우 엔진과 내장된 Human-in-the-loop 기능을 통해 Go 언어로 안정적이고 복잡한 멀티 에이전트 애플리케이션을 쉽게 만들 수 있도록 도와줘요.
인사이트
- ADK Go 2.0은 그래프 기반 워크플로우 엔진을 도입해서 복잡한 에이전트 앱의 오케스트레이션을 직관적으로 만들고, 안정성과 재개 가능성을 높였어요.
- Human-in-the-loop (HITL) 기능이 내장되어 있어, 에이전트 워크플로우 중간에 사람의 승인이나 개입이 필요할 때 프로세스를 잠시 멈추고 다시 시작할 수 있게 해줘요.
- 일반 Go 코드로 동적 오케스트레이션을 구현할 수 있어서, 실행 순서가 런타임에 결정되는 유연한 워크플로우를 만들 수 있고, 모든 에이전트와 워크플로우가 통일된 런타임 모델 위에서 작동해요.
왜 중요한가
현실 세계의 에이전트 애플리케이션은 단순한 프롬프트 하나로 해결되지 않는 복잡한 로직이 필요해요. ADK Go 2.0은 이런 복잡한 분류, 분기, 인간 개입, 오류 재시도, 루프 같은 오케스트레이션을 그래프 기반 워크플로우와 내장된 Human-in-the-loop 기능을 통해 Go 개발자들이 Go스럽게, 그리고 안정적으로 구현할 수 있게 해줘요. 덕분에 프로덕션 환경에 적합한 강력한 멀티 에이전트 시스템을 훨씬 쉽고 견고하게 만들 수 있게 되었죠.
2026년 6월 30일
토니 클로펜슈타인 ADK 개발자 관계팀 소속 개발자 관계 엔지니어
ADK for Go 2.0: 그래프로 에이전트 워크플로우를 만들어 보세요
실제 에이전트 애플리케이션을 만드는 건 보통 프롬프트 하나 보내는 것만큼 단순하지 않아요. 프로덕션 에이전트는 분류하고, 분기하고, 여러 곳으로 작업을 뿌리고(fan out), 사람에게 승인을 요청하고, 실패하면 재시도하고, 완료될 때까지 반복해야 하죠. 이렇게 복잡한 오케스트레이션을 그때그때 임시방편(ad-hoc)으로 제어 흐름을 표현하면 금방 깨지기 쉬워져요.
1.0 버전이 출시된 이후로, ADK(Agent Development Kit) for Go는 Go 개발자들이 깔끔하고 Go스러운 API — 강력한 타입 지정, iter.Seq2 이벤트 스트림, 그리고 기존 Go 서비스에 자연스럽게 녹아드는 런타임을 통해 프로덕션 에이전트를 만들도록 도와줬어요. 이런 탄탄한 기반 덕분에 다음 단계로 나아갈 수 있었죠.
오늘 우리는 ADK for Go 2.0을 공개하게 되어 정말 기뻐요. 핵심은 멀티 에이전트 애플리케이션을 구성하는 새롭고 근사한 방법인데요, 바로 그래프 기반 워크플로우 엔진이에요. 이와 함께 **Human-in-the-loop (HITL)**가 내장된 기본 요소로 들어갔고, 일반 Go로 작성된 동적 오케스트레이션, LLM 에이전트 모드, 그리고 이 모든 것을 통합하는 통일된 노드 런타임도 함께 제공돼요. 이제 단일 에이전트와 전체 그래프 모두 동일한 실행 모델에서 작동하죠.
만약 Python ADK 2.0을 지켜보셨다면, 아마 익숙하게 느껴질 거예요. 파이썬 버전과 동일하게 그래프 중심 방향을 따르면서도, 처음부터 Go 언어의 특징에 잘 맞도록 설계되었거든요.
왜 그래프인가요?
실제 에이전트 애플리케이션은 단일 프롬프트로만 구성되는 경우가 거의 없어요. 대개는 분류하고, 분기하고, 전문가들에게 작업을 분배하고, 결과를 모으고, 사람에게 승인을 요청하고, 실패 시 재시도하고, 완료될 때까지 반복해야 하죠. 이런 복잡한 과정을 임시방편적인 제어 흐름으로 표현하면 금방 깨지기 쉬워져요.
ADK 2.0은 애플리케이션의 _형태_를 엣지로 연결된 노드들의 그래프로 표현할 수 있게 해주고, 실행은 스케줄러에게 맡겨요. 이 스케줄러는 워크플로우를 동시에 실행하고, 상태를 영구적으로 저장하고, 사람을 위해 잠시 멈췄다가 나중에 다시 시작하는 방법을 알고 있죠. 심지어 프로세스가 재시작된 후에도 가능해요. 노드를 이렇게 간단하게 연결할 수 있어요:

import "google.golang.org/adk/v2/workflow"
upper := workflow.NewFunctionNode("upper", upperFn, cfg)
suffix := workflow.NewFunctionNode("suffix", suffixFn, cfg)
edges := workflow.Chain(workflow.Start, upper, suffix)
wf, _ := workflowagent.New(workflowagent.Config{
Name: "simple_sequence_workflow",
Edges: edges,
})
Go
복사 완료
이 wf는 그냥 agent.Agent일 뿐이에요. 여러분이 이미 사용하고 있는 동일한 러너, 런처, 콘솔에서 실행되죠. 특별한 하네스나 새로운 서버가 필요 없어요. 그래프 자체가 에이전트인 셈이에요.
구성 요소들
모든 것을 위한 노드
노드는 Node 인터페이스를 구현하는 모든 작업 단위를 말해요. 이 인터페이스를 직접 손으로 작성하는 경우는 거의 없고요. ADK는 일반적인 경우를 위해 타입이 지정된 노드 생성자를 제공해요:
- **함수 노드(Function nodes)**는 평범한 타입이 지정된 Go 함수를 감싸는 역할을 해요. 제네릭이 여러분을 대신해 입력/출력 스키마를 추론해 줘요:
workflow.NewFunctionNode("classify",
func(ctx agent.Context, in string) (Category, error) { ... }, cfg)
Go
복사 완료
- **이벤트 방출 함수 노드(Emitting function nodes)**는
emit콜백을 함께 받는 함수 노드예요. 덕분에 단일 함수로 이벤트를 스트리밍하거나 사람을 위해 잠시 멈출 수 있고, 이를 위해 굳이 동적 노드까지 내려가지 않아도 돼요:
workflow.NewEmittingFunctionNode("progress",
func(ctx agent.Context, in Job, emit func(*session.Event) error) (Result, error) { ... }, cfg)
Go
복사 완료
- **에이전트 노드(Agent nodes)**는 어떤
agent.Agent든 (예를 들어LlmAgent같은) 그래프 안에 배치할 수 있게 해줘요. - **도구 노드(Tool nodes)**는
tool.Tool을 그래프의 한 단계로 만들어 줘요. - **조인 노드(Join nodes)**는 팬인(fan-in) 장벽 역할을 해요. 모든 선행 노드가 완료될 때까지 기다렸다가 그들의 출력을 맵 형태로 넘겨주죠.
- **동적 노드(Dynamic nodes)**는 코드 안에서 오케스트레이션을 할 수 있게 해줘요 (자세한 내용은 아래에서 더 다룰게요).
- **워크플로우 노드(Workflow nodes)**는 전체 하위 워크플로우를 단일 노드로 포함할 수 있어요. 즉, 그래프는 서로 조합될 수 있다는 뜻이죠.
- **병렬 워커(Parallel workers)**는 목록의 모든 항목에 대해 노드를 동시에 실행하고 결과를 집계해요.
- 상태 바운드 노드(State-bound nodes) (
NewFunctionNodeFromState)는state:"<key>"태그를 통해 선택된 세션 상태 값을 타입이 지정된 Params 구조체로 바로 가져와요. 수동으로 상태를 연결할 필요가 없어요.
엣지, 라우팅, 그리고 필요한 형태들
엣지는 노드를 연결하고 라우팅 조건을 가질 수 있어요. 한 노드가 라우팅 값을 방출하면, 일치하는 엣지가 활성화되는 식이죠. 이 하나의 개념으로 여러분이 필요한 모든 제어 흐름 형태를 구현할 수 있어요:
b := workflow.NewEdgeBuilder()
b.AddRoutes(router, map[string]workflow.Node{
"question": answerNode,
"statement": commentNode,
"exclamation": reactNode,
})
b.AddFanOut(planner, researchA, researchB, researchC) // parallel branches
b.AddFanIn(join, researchA, researchB, researchC) // gather results
Go
복사 완료
순차적 체인, 조건부 라우터, 팬아웃/팬인, 중첩된 하위 그래프, 심지어 루프(완료된 노드가 다시 트리거될 수 있어서 순환도 기본 기능이에요)까지 — 이 모든 것이 엣지와 라우트를 통해 가능하죠. 표준 라우트에는 StringRoute, IntRoute, BoolRoute, MultiRoute가 있으며, 다른 어떤 것도 일치하지 않을 때 활성화되는 Default도 있어요. 더 자세한 설정을 원하면 Route 인터페이스를 활용해 보세요.
LLM이 그래프를 조종하게 해보세요
가장 유용한 패턴 중 하나는 모델을 라우터의 _두뇌_로 사용하는 거예요. LlmAgent가 사용자의 메시지를 분류하면, 간단한 함수가 일치하는 라우트를 방출하고, 그래프는 올바른 핸들러로 작업을 보내죠:
User -> What time is it? Agent -> question answering question...
User -> Hello world! Agent -> exclamation reacting to exclamation...
User -> The sky is blue. Agent -> statement commenting on statement...
일반 텍스트
복사 완료
모델이 결정을 내리고요, 그래프는 그 결정을 안정적이고, 관찰 가능하며, 재개할 수 있도록 만들어 줘요. (examples/workflow/routing/llm/ 예시를 참고해 보세요.)
동적 오케스트레이션 — 일반 Go 코드로
가끔 실행 순서가 런타임까지 알 수 없는 경우가 있어요. 데이터에 따라, 루프 횟수에 따라, 혹은 모델이 방금 뭐라고 말했는지에 따라 달라질 수 있죠. 이런 경우를 위해 ADK 2.0은 **동적 노드(dynamic nodes)**를 제공하는데요, 여기서 오케스트레이션 본문은 각 자식 노드에 대해 RunNode(...)를 호출하는 일반 Go 코드예요:
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{},
)
Go
복사 완료
루프, 조건문, 누적, 동적 목록 전반에 걸친 팬아웃 — 이 모든 것이 여러분이 이미 알고 있는 Go 언어로 표현될 수 있어요. WithRunID, WithUseSubBranch, WithUseAsOutput, WithIsolationScope와 같은 옵션들은 자식 노드의 ID, 이력 격리, 출력 위임에 대한 정밀한 제어권을 제공하죠. 이 기능은 Python ADK의 동적 그래프에 해당하는 Go 버전이라고 볼 수 있어요.
내장된 Human-in-the-loop
프로덕션 에이전트는 종종 실행 도중에 사람의 승인, 수정 또는 정보 제공이 필요할 때가 있어요. ADK 2.0에서는 어떤 노드든 그래프를 일시 중지하고 사람에게 질문을 할 수 있어요 — 그러면 워크플로우는 응답을 받을 때까지 안정적으로 기다리죠:
event := workflow.NewRequestInputEvent(ctx, session.RequestInput{
InterruptID: "approve_refund",
Message: "Approve a $200 refund? (yes/no)",
ResponseSchema: schema,
})
// yield the event; the node moves to "waiting"
Go
복사 완료
나중에 사람이 응답하면 워크플로우는 다시 시작돼요. 재개 방식은 여러분이 선택할 수 있어요:
- 핸드오프(Handoff) — 응답이 다음 노드로 바로 흘러가요.
- 재진입(Re-entry) — 일시 중지된 노드가
ctx.ResumedInput(...)을 통해 사람의 응답을 받아 다시 실행돼요.
그리고 재개 기능은 영속적이에요. 실행 상태는 세션에 저장되고, ADK는 세션 기록을 스캔하여 일시 중지된 워크플로우를 재구성할 수도 있어요. 덕분에 프로세스 재시작 후에도, 심지어 다른 런타임에서도 워크플로우를 재개할 수 있죠. 인터럽트 형식이 Python ADK와 공유되기 때문이에요. 응답은 스키마에 따라 유효성 검사를 거치고, 재개는 멱등하며, 뭔가 맞지 않을 때는 명확한 오류 메시지(ErrInvalidResumeResponse, ErrNothingToResume)를 받을 수 있어요.
콘솔 런처와 웹 UI 모두 Human-in-the-loop (HITL) 기능을 기본적으로 이해해서 도구 확인 프롬프트와 워크플로우 입력 요청을 모두 표시해 줘요.
상용구 코드 없이 복원력 확보하기
모든 노드는 지수 백오프(exponential backoff)와 지터(jitter)를 포함한 재시도 정책을 가질 수 있어요. 외부 의존성 없이 말이죠:
cfg := workflow.NodeConfig{ RetryConfig: workflow.DefaultRetryConfig() }
// 5 attempts, 1s initial delay, 60s cap, 2x backoff, full jitter
Go
복사 완료
노드별 Timeout을 추가하고, WithMaxConcurrency(n)으로 그래프 전체의 동시성(concurrency)을 제한하며, 병렬 브랜치를 격리해서 한 브랜치의 잡담이 다른 브랜치의 LLM 프롬프트 기록으로 새어 나가지 않도록 할 수 있어요. 스케줄러가 고루틴, 채널, 역압(backpressure), 그리고 취소 기능을 대신 처리해 줘요.
에이전트 모드와 이 모든 것을 실행하는 하나의 런타임
ADK 2.0은 LLM 에이전트를 위한 모드 — Chat, Task, SingleTurn — 를 도입했어요. 덕분에 코디네이터가 사용자와 대화하는 동안 하위 에이전트는 조용히 작업을 완료하거나 단발성 작업을 실행할 수 있죠. 각 에이전트의 역할에 따라 적절한 헬퍼 도구(finish_task, single_turn, task)가 자동으로 설치돼요.
내부적으로, 러너는 이제 워크플로우를 구동하는 동일한 노드 런타임을 통해 일반 LlmAgent를 구동해요. 그 결과, 단일 에이전트 앱과 전체 그래프가 하나의 실행 모델을 공유하게 되고, Human-in-the-loop (HITL) 기능이 워크플로우 내부뿐만 아니라 일반 LLM 에이전트에서도 작동하게 된 거죠.
ADK 팀은 프로그래밍 모델도 개선했어요: ToolContext와 CallbackContext가 이제 agent.Context 하나로 통합되었어요. 도구를 작성하든, 콜백을 작성하든, 그래프 노드를 작성하든 배워야 할 타입이 하나로 줄어든 거죠. 그리고 노드/에이전트 실행은 일관된 텔레메트리 스팬 트리(telemetry span tree)에 표시되어서, 그래프가 정확히 어떤 일을 했는지 볼 수 있어요.
1.0에서 업그레이드하기
ADK 2.0은 대부분 새로운 기능 추가예요. 전체 워크플로우 엔진은 여러분이 선택해서 사용할 수 있는 새로운 패키지들이죠. 런타임을 통합하면서 몇 가지 새롭고 호환성이 깨지는 변경 사항이 생겼지만, 각각 간단하고 기계적인 수정으로 해결할 수 있어요:
- 노드 및 노드 함수 시그니처에**
agent.Context**가 사용돼요. 노드나 노드 함수를 작성하고 있다면, 첫 번째 파라미터를agent.InvocationContext에서agent.Context로 변경해야 해요. (InvocationContext를 포함하고 있어서 이전에 사용하던 모든 메서드는 여전히 작동할 거예요.)
// before: func(ctx agent.InvocationContext, in string) (string, error)
// after: func(ctx agent.Context, in string) (string, error)
- 하나로 통합된 컨텍스트.
ToolContext,CallbackContext는 이제 사라졌어요. 도구, 콜백, 워크플로우 노드 모두agent.Context를 직접 받게 되죠. 테스트에서 컨텍스트를 모킹했다면,agent/context_mock.go파일은 그대로 남아 있으니 해당 파일의StrictContextMock을 테스트 더블(test double)로 사용하면 돼요. - 사용자 정의**
InvocationContext**구현은 두 가지 메서드IsolationScope()와ResumedInput(id string)가 필요해요. 대부분의 코드는 제공된 구현을 포함하고 있기 때문에 이 메서드들을 그냥 사용할 수 있어요. - 이벤트 스트림이 더 풍부해졌어요. 이제 이벤트는 노드 필드(
IsolationScope,Output,Routes,RequestedInput)와 메타데이터 필드(NodeInfo)를 포함해요. 테스트에서 정확한session.Event동등성을 단언(assert)한다면, 새로운 필드들을 예상해야 할 거예요. 사용자 정의 세션 스토어는 이 필드들을 유지해야 하고요. llmagent.New가 모드별 도구를 설치할 수 있어요. 하위 에이전트 모드를 설정하면, 실제 도구 세트는 그 모드를 반영하게 돼요.task모드 에이전트는 정적 그래프 노드로 사용할 수 없고요.- session.NewEvent가 컨텍스트를 받아요. 이제 시그니처는
NewEvent(ctx context.Context, invocationID string)예요. 호출 지점에서 이미 스코프 내에 있는context.Context를 첫 번째 인수로 전달하도록 마이그레이션하세요.
이게 변경 사항 전부예요. runner.Run/RunLive, agenttool, 그리고 llmagent 콜백의 공개 시그니처는 변경되지 않았어요. 단계별 전/후 지침을 보려면 ADK Go 2.0 마이그레이션 가이드를 확인해 보세요.
직접 사용해보기
ADK 2.0을 가장 빠르게 체험해볼 수 있는 방법은 새로운 워크플로우 예시를 살펴보는 거예요:
go run ./examples/workflow/basic/
go run ./examples/workflow/routing/llm/ # LLM-as-router
go run ./examples/workflow/dynamic/hitl/ # dynamic + human-in-the-loop
go run ./examples/workflow/hitl_rerun/ # HITL with re-entry resume
go run ./examples/workflow/complex/ # a larger, multi-shape graph
셸
복사 완료
ADK 1.0은 Go로 진지한 에이전트를 구축하는 것이 깔끔하고 생산적일 수 있다는 걸 증명했어요. ADK 2.0은 여기서 한 단계 더 나아가서, 그 에이전트들을 안정적이고, 관찰 가능하며, 재개 가능한 워크플로우로 구성할 수 있게 해줘요 — 그래프 형태로, Go 언어의 관용적인 방식으로, 그리고 중요한 순간에는 사람의 개입까지 포함해서 말이죠.
여러분들이 무엇을 만들어낼지 정말 기대돼요.
— ADK for Go 팀
다음