에이전트 플랫폼 활용기 (1): Go와 Google ADK 2.0으로 LINE AI 비즈니스 카드 마스터 구축하기: Fan-Out 병렬
요약
기존 LLM 에이전트가 복잡하고 순차적인 작업 흐름에서 통제력을 잃거나 비효율적이라는 문제점을 지적합니다. 이에 Google은 ADK 2.0에 Graph Workflows를 도입했으며, 필자는 Go 언어와 LINE Messaging API SDK v8을 활용하여 Fan-Out 병렬 처리 기능을 갖춘 'LINE AI Business Card Master' 에이전트를 구축하는 과정을 공유합니다.
핵심 포인트
- LLM 에이전트는 복잡한 다단계 작업에서 통제력 상실 및 비효율성이 발생할 수 있다.
- Google ADK 2.0은 Graph Workflows를 도입하여 조건부 라우팅, 병렬 처리(Fan-Out), 대기 기능을 공식 지원한다.
- Go 언어와 LINE SDK v8을 사용하여 실제 운영 환경에 적용 가능한 오픈 소스 에이전트 프로젝트를 구현했다.
회고 (Recap)
지난 1년간 AI Agent 개발 열풍 속에서, 많은 개발자들이 처음 작성한 에이전트는 종종 다음과 같은 형태였습니다: 믿을 수 없을 만큼 긴 System Instruction을 작성하고, 다섯 개 또는 여섯 개의 Tool을 동일한 LLMAgent에 채워 넣은 다음, 모델이 A → B → C → D 단계를 순순히 따르기를 기도하는 식이었습니다.
이는 초기에 시연(Demo)으로 볼 때는 인상적이지만, 실제 운영 환경에 투입되는 순간 문제가 즉시 발생합니다:
- 단계가 많을수록 통제력을 잃을 확률이 높아진다: 만약 프롬프트에 "먼저 OCR을 수행하고, 다음 중복 여부를 데이터베이스에서 확인한 후, 이 회사가 무엇을 하는지 온라인으로 동시에 검색하고, 사용자에게 보여줄 미리보기 카드를 조립한 뒤, 사용자가 직책 변경을 원하면 미리보기를 재생성하고, 확정 후에만 DB에 기록하고 아이스브레이커 이메일을 작성하라"와 같은 내용이 채워져 있다면, 모델은 가끔 중복 확인 단계를 건너뛰거나 사용자 확인을 기다리지 않고 바로 데이터베이스에 쓰기 작업을 수행합니다.
- 토큰을 쓸 필요가 없는 곳에서 돈을 태우고 대기한다: "LINE 이벤트 유형 기반으로 분배(shunting)"하거나 "전화번호 형식을 표준화(normalizing phone number formats)"하는 것과 같이 100% 결정론적인 로직은 LLM에 맡기는 것이 느릴 뿐만 아니라 오류가 발생하기 쉽습니다.
- 독립적인 작업들이 대기열에서 강제로 실행된다: 데이터베이스 확인, 회사 배경 구글 검색, 최근 대화 기록 비교 등은 명백히 서로 독립적임에도 불구하고, 단일 에이전트의 선형적인 대화 루프 때문에 순차적인 대기 과정이 됩니다.
이러한 문제점을 해결하기 위해 Google은 Agent Development Kit (ADK) 2.0에서 무거운 기능을 공식적으로 도입했습니다 — 바로 Graph Workflows이며, Python과 **Go (google.golang.org/adk/v2/workflow)**를 동시에 지원합니다!
이 업데이트를 보고, 저는 가장 익숙한 Golang + LINE Messaging API SDK v8을 사용하여 완전한 오픈 소스 프로젝트인 **line-card-assistant (LINE AI Business Card Master)**를 구축하기로 즉시 결정했습니다. ADK 2.0 Graph Workflow의 모든 핵심 기능들(조건부 라우팅을 위한 AddRoute, 병렬 처리 및 집계를 위한 AddFanOut / AddFanIn, Human-in-the-Loop 일시 중지/대기 및 루프 수정을 위한 NewRequestInputEvent)을 실제 환경에서 테스트하고 Google Cloud Run에 배포했습니다.
이 글에서는 먼저 Google의 공식 ADK Graph Workflows를 위해 반드시 읽어야 할 두 가지 핵심 기술 문서를 안내한 후, 우리가 Go 코드를 사용하여 이러한 그래프 워크플로우 패턴을 LINE Bot에 어떻게 구현했는지 단계별로 자세히 설명할 것입니다!
I. Google은 Graph Workflow를 어떻게 정의하는가? 필독 가이드 2개 분석
코드 보기에 앞서, ADK 팀이 설계한 Graph Workflows의 핵심 철학을 이해하는 것이 강력하게 권장됩니다. Google은 최근 두 개의 매우 중요한 개발자 가이드를 발표했습니다:
- Google Cloud 공식 블로그: Graph Workflows in ADK: Everything You Need to Know (ADK 팀의 Annie Wang 및 Shangjie Chen 작성)
- ADK 공식 기술 문서: Graph-based agent workflows (adk.dev/graphs)
저희는 이 두 가지 공식 기사의 핵심 내용을 다음과 같이 요약하고 정리했습니다:
1. '그래프 엔지니어링(Graph Engineering)'이란 무엇인가?
기사 'Graph Workflows in ADK: Everything You Need to Know'에서 저자들은 '고객 환불 워크플로우(Customer Refund Workflow)'를 예시로 사용하여 핵심 개념을 지적합니다:
워크플로우가 복잡해짐에 따라, 신뢰성은 '워크플로우 제어(Workflow Control)'와 '모델 추론(Model Reasoning)'을 분리하는 것에서 나옵니다. 그래프 엔지니어링의 핵심은 작업을 독립적인 노드로 분해하고, 이를 엣지로 연결하며, 각 단계마다 다음 단계를 누가 결정할지('코드', '모델', 또는 '사람')를 명시적으로 결정하는 것입니다.
본 기사는 실제 세계에서 가장 흔하게 사용되는 그래프 워크플로우 디자인 패턴 다섯 가지 중 다섯 가지를 요약합니다:
| 핵심 패턴 | 어떤 문제점을 해결하나요? | ADK 2.0 대응 메커니즘 |
|---|---|---|
| 1. Fan-out 및 Fan-in 병렬 집계 | 여러 독립적인 데이터 수집 단계(예: 주문, 결제, 고객 기록 확인)가 순차적으로 실행되면 전체 지연 시간이 느려집니다. | AddFanOut을 사용하여 여러 노드가 병렬로 실행되도록 트리거한 다음, 모든 분기가 완료될 때까지 기다린 후 집계하기 위해 JoinNode (AddFanIn)를 사용하며, 이 과정에서 추가적인 LLM 호출은 전혀 소모되지 않습니다. |
| 2. 결정론적 라우팅 대 에이전트 라우팅 | 일부 분기 규칙은 명확하게 '흑백'입니다(예: 금액 > $100은 수동 검토로 이동) 따라서 LLM이 추측해서는 안 되며, 다른 규칙들은 자연어 의도를 이해할 필요가 있습니다. | 결정론적 라우터: 순수 코드 로직이 ev.Routes를 설정하며, 토큰 소모 없음, 100% 테스트 가능합니다. |
| 에이전트 라우터: LLM이 의도를 판단하지만, 그래프 엣지는 유효한 점프 대상 범위를 엄격하게 제한합니다. | ||
| 3. 인간 개입형(HITL) 일시 중지 및 재개 | 고위험 작업(예: 대규모 환불, 프로덕션 데이터베이스 쓰기)은 사람이 확인해야 하지만, 전통적인 웹훅(Webhooks)은 상태를 저장하지 않는 단기 연결입니다. | 노드가 RequestInput 이벤트를 발행하여 실행 상태를 동결(Checkpoint)하고 그래프를 일시 중지합니다. 외부의 인간 응답을 받은 후, RerunOnResume이 동일한 노드를 깨워 계속 진행하게 합니다. |
| 4. 병렬 워커 대 |
Fan-out** | 동일 데이터에 대해 '세 가지 다른 검사를 수행하는 것'과 'N개 항목에 동일한 처리를 수행하는 것'을 구분합니다. | Fan-out: 서로 다른 노드가 각기 다른 책임을 수행합니다.
Parallel Workers: 동일한 노드가 배열 요소별로 병렬 확장됩니다. |
| 5. Static Graph vs. Dynamic Orchestration | '아키텍처 시각화/유지보수성'과 '동적 루프 수정' 사이의 균형을 맞추는 방법은 무엇일까요? | 정적인 Edges를 사용하여 명확한 라우팅을 정의하고 **조건부 사이클(Conditional Cycles) (예: 초안 생성 → 확인/수정 → 재생성)**을 만듦으로써, 시스템 전체의 상태 머신을 한눈에 파악할 수 있게 합니다. |
2. ADK Go v2.0의 네 가지 핵심 빌딩 블록 (adk.dev/graphs)
공식 문서 Graph-based agent workflows에는 **Go 언어(google.golang.org/adk/v2/workflow)**에서 ADK 2.0의 API 설계가 상세히 나와 있습니다. Go로 그래프를 구성하려면 주로 다음 네 가지 컴포넌트를 숙지해야 합니다:
- Nodes (노드):
workflow.NewFunctionNode/workflow.NewEmittingFunctionNode: 모든 표준 Go 함수를 그래프 노드로 래핑하여, DB 쿼리, 데이터 정리, API 호출 또는 LINE Flex 메시지 조립에 적합합니다.llmagent.RunLLMAgentAsNode: 특정 프롬프트와 도구(예:geminitool.GoogleSearch{})를 가진llmagent를 단일 노드로 캡슐화하여, LLM이 '하나의 일'에 집중하도록 합니다.
- State & Data Flow (상태 및 데이터 흐름):
- 노드 간에 강력하게 타입 지정된
Output을 전달하는 것 외에도, 구조화된 데이터를 세션 상태(ctx.Session().State())에ev.Actions.StateDelta를 통해 기록할 수 있으며, 이는 다운스트림 또는 집계 노드가 읽어갈 수 있습니다.
- 노드 간에 강력하게 타입 지정된
- Edges & EdgeBuilder (엣지 및 엣지 빌더):
eb.Add(from, to): 단방향 직선 연결입니다.- `eb.AddRoute(from, workflow.StringRoute(
eb.AddFanOut(from, branch1, branch2, ...)와eb.AddFanIn(joinNode, branch1, branch2, ...): 병렬 분기 및 동기적 집계입니다.
- WorkflowAgent:
- 마지막으로,
workflowagent.New(workflowagent.Config{Edges: edges})를 호출하면 전체 그래프가 표준 ADKagent.Agent가 되며, 이는 실행 및 세션 관리를 위해runner.Runner에 전달됩니다!
- 마지막으로,
II. 실전 프로젝트 아키텍처: LINE AI 비즈니스 카드 마스터 (line-card-assistant)
공식 이론을 이해했으니, 이러한 개념들을 실제 LINE 봇 시나리오에 어떻게 적용할 수 있는지 살펴보겠습니다.
비즈니스 전문가들은 매일 전시회나 회의에서 많은 양의 종이 명함을 받습니다. 기존의 비즈니스 카드 앱들은 단순한 OCR 인식 기능만 제공할 뿐, "최근 이 회사는 어떤 활동을 했는가?", "어떤 행사에서 이 사람을 만났었나?", 그리고 **"채팅방에서 말로 직접 인식 오류를 수정하는 방법은 무엇인가?"**와 같은 기능이 부족합니다.
따라서, 우리는 ADK 2.0의 그래프 워크플로우에 완벽하게 대응하는 전체 아키텍처를 가진 line-card-assistant를 설계했습니다:
[LINE Webhook]
│
▼
...
이 아키텍처 다이어그램은 세 가지 핵심 경로를 다룹니다:
- 명함 인식 및 정보 보강 파이프라인 (Ingest Pipeline): 사진 수신 → 광학 문자 인식(OCR) 비전 처리 → 세 가지 작업을 병렬로 실행 (DB 중복 확인 + Google 검색을 통한 회사 배경 조사 + 미팅 상황에 대한 대화 맥락 추론) → 정보를 취합하여 LINE Flex 미리보기 카드 생성 → 사용자 확인 또는 수정을 위해 HITL(Human-in-the-Loop) 일시 정지.
- HITL 루프 수정 (Re-entry Loop): 그래프가
HITLConfirmNode에서 일시 정지된 상태일 때, 사용자가 LINE에 직접 텍스트를 입력하는 경우 (예: "직책을 Director로 변경함, Cloud Summit에서 만남"), 워크플로우가 활성화되어RouteEdit분기를 거쳐ApplyEditNode로 진입하고, 필드를 업데이트한 후 미리보기 카드를 재생성하기 위해BuildPreviewNode로 루프하며 다시 일시 정지합니다. 이 과정은 사용자가 "✅ Confirm Save"를 클릭하여 실제로 데이터베이스에 쓰기 작업을 수행하고 아이스브레이커 이메일을 생성할 때까지 반복됩니다! - 자연어 하이브리드 검색 파이프라인 (Search Pipeline): 쿼리 텍스트 수신 (예: "지난주 Cloud Summit에서 만난 AI 관련 사람들을 찾아줘") → 쿼리 재작성 구조 분해 → SQL + 키워드/태그 하이브리드 검색 → LINE Flex 캐러셀 형태의 명함 회전 표시 반환.
III. Go 코드 심층 분석: 그래프 워크플로우를 LINE 봇에 작성하기
다음으로, internal/workflow/graph.go 파일과 다양한 노드를 직접 살펴보며 Go에서 ADK 2.0 API가 얼마나 깔끔하게 보이는지 확인해 보겠습니다.
1. 전역적 관점: EdgeBuilder를 이용한 그래프 조립
internal/workflow/graph.go 파일에서, 우리는 BuildEdges 함수를 통해 모든 노드를 선언하고 연결합니다. 전체 그래프의 위상적 구조가 명확하게 보입니다:
func BuildEdges(deps *Dependencies) ([]workflow.Edge, error) {
// 1. 진입 라우터 노드 (Entry Router Node)
routerNode, _ := NewRouterNode()
...
이 코드는 Google Cloud Blog에서 언급된 **정적 그래프 이점(Static Graph advantage)**을 완벽하게 구현합니다. 수천 줄의 프롬프트를 일일이 확인하거나 여기저기에 흩어져 있는 Tool Call을 추적할 필요가 없습니다. BuildEdges에 있는 단 30줄의 코드를 보고도 전체 Agent 시스템의 모든 가능한 경로와 루프 경계를 100% 파악할 수 있습니다.
2. 결정론적 라우터(Deterministic Router): 토큰 소비 없이 정밀한 샤팅 구현
LINE Webhook이 이벤트를 수신할 때, 모델에게 "사용자가 사진을 보내는지 아니면 텍스트를 보내는지?"라고 물어보기 위해 Gemini API 호출을 낭비할 필요가 없습니다. 우리는 workflow.NewEmittingFunctionNode를 사용하여 순수 코드 기반의 결정론적 라우팅 노드인 RouterNode를 생성합니다:
const (
RouteImage = "route_image"
RouteText = "route_text"
...
workflow.Event를 발행할 때 ev.Routes = []string{route}를 설정하고, 구성 중에 라우트의 유효성을 검증하기 위해 workflow.WithDeclaredRoutes(...)를 사용하면, ADK가 시작 단계에서 오타가 난 Route 이름을 자동으로 확인합니다!
3. 팬아웃/팬인 병렬 처리(Fan-Out / Fan-In Parallel Processing): DB 중복 확인, Google 검색, 컨텍스트 추론 동시 실행
VisionOCRNode가 gemini-2.5-flash의 멀티모달 기능을 통해 명함 사진에서 구조화된 models.BusinessCard를 추출하면, 우리는 수행해야 할 세 가지 완전히 독립적인 작업을 갖게 됩니다:
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기