ADK의 그래프 워크플로: 알아야 할 모든 것

Annie Wang
Google AI Cloud Developer Advocate
Shangjie Chen
Software Engineer, Google Cloud AI
*이 콘텐츠는 AI 번역 도구를 사용하여 번역되었습니다. 정확성을 기하기 위해 노력하고 있으나, 자동 번역에는 오류나 누락이 포함될 수 있습니다. 정확한 정보는 영문 원본을 참고해 주시기 바랍니다.
그래프 엔지니어링은 일종의 설계 작업으로, 작업을 노드로 분할하고, 에지에 연결하고, 코드, 모델 또는 사람이 다음 단계를 제어할 위치를 결정하는 과정입니다. 에이전트 개발 키트(ADK)의 워크플로는 이러한 설계를 실행 가능한 프로세스로 전환하여 함수와 에이전트가 작업을 수행하도록 합니다. 이 게시물에서는 환불 예시를 통해 단계를 동시에 실행하고, 결정을 라우팅하고, 사람의 검토를 위해 일시중지하고, 케이스 목록을 처리하는 방법을 보여줍니다. 또한 정적 그래프에서 경로를 선언해야 하는 경우와 결과가 도착하면 Python이 추가 작업을 예약하도록 해야 하는 경우를 설명합니다.
요약: ADK의 환불 워크플로를 사용하여 팬아웃 및 팬인, 결정론적 라우터 및 에이전트 라우터, 인간 참여형 일시중지, 동시 작업자, 동적 조정에 대해 알아보고, 정적 그래프를 사용해야 하는 경우와 Python이 다음에 실행할 작업을 결정하도록 해야 하는 경우를 살펴봅니다.
단일 에이전트로 시작하기
특정 에이전트에 환불 요청을 처음부터 끝까지 처리할 수 있는 도구와 안내를 제공할 수 있습니다.
이렇게 하면 모델이 도구를 선택하고, 정책을 적용하고, 답장을 작성하는 역할을 담당하게 됩니다. 하지만 프롬프트에는 이미 세 가지 조회, 결정, 응답이라는 개별 작업이 설명되어 있습니다. 이러한 작업을 별도의 노드로 분할하면 각 작업을 어떻게 실행해야 하는지 결정할 수 있습니다.


고객이 주문을 선택하고 '환불 요청'을 클릭했다고 가정해 보겠습니다. 앱은 주문 ID를 알고 있으므로 조회 작업부터 워크플로를 시작할 수 있습니다.
동시에 독립적인 단계 실행하기
주문, 결제, 환불 내역 조회 작업에는 모두 주문 ID가 필요하지만 다른 조회 작업의 결과는 필요하지 않습니다. 프롬프트에는 이러한 작업이 순서대로 나열되어 있지만 각 작업이 완료되기까지 기다릴 이유가 없습니다. 세 가지 작업을 모두 동시에 실행할 수 있습니다.
하지만 정책 결정 작업은 성격이 다릅니다. 이 작업에는 세 가지 레코드가 모두 필요합니다. 따라서 워크플로를 세 개 경로로 분할한 다음 계속 진행하기 전에 그 결과를 결합합니다. 이 두 가지 과정을 각각 팬아웃과 팬인이라고 합니다.
ADK에서는 조회 함수가 그 자체로 노드로 사용될 수 있습니다. 중첩된 튜플을 사용하면 조회 함수가 동시에 시작되고 JoinNode가 그 결과가 도착할 때까지 기다립니다. 먼저 가져오기 및 조회 서명을 살펴보겠습니다.
각 조회 작업은 node_input을 통해 주문 ID를 수신하고 사전을 반환합니다. 이를 다음과 같이 연결할 수 있습니다.
왼쪽에서 오른쪽으로 읽어보세요. 먼저 세 가지 조회 작업을 모두 시작한 다음 완료되면 join_case 작업을 계속 진행합니다.


조인은 노드 이름으로 키가 지정된 사전을 반환합니다. 다음 노드는 모델 호출 없이 node_input["fetch_order"], node_input["fetch_payment"], node_input["fetch_history"]를 읽어 결과를 수집할 수 있습니다. 자세한 내용 JoinNode 가이드를 참조하세요.
결제 조회 작업에 주문 조회의 거래 ID가 필요한 경우 두 작업은 순차적으로 실행됩니다. 프롬프트에 모든 단계가 순서대로 나열되어 있더라도 종속 항목에 따라 에지가 결정됩니다.
다음 동영상에서 팬아웃과 팬인에 대해 자세히 알아볼 수 있습니다.

도구를 노드로 변환할 때 한 가지 중요한 세부정보가 있습니다. 바로 node_input이라는 파라미터가 이전 노드의 출력을 수신합니다. 다른 이름의 파라미터는 기본적으로 ctx.state에 바인딩되므로 order_id는 ctx.state["order_id"]를 확인하고 누락된 경우 ValueError를 발생시킵니다. 여기서 str 주석은 START의 types.Content 입력을 문자열로 변환합니다.
각 요청을 적절한 워크플로로 라우팅하기
지금까지 고객은 명시적으로 환불을 요청했습니다. 더 광범위한 지원 대화에서는 먼저 고객이 원하는 것이 무엇인지 파악하고 요청을 적절한 프로세스로 전송해야 합니다. "신발 사이즈가 맞지 않아요. 다른 제품으로 보내 주실 수 있나요?"라는 요청은 교환 워크플로로, 환불 요청은 환불 워크플로로 전달되어야 합니다.
여기서 다음에 실행할 브랜치를 선택하는 노드인 라우터라는 개념이 등장합니다.
- 결정론적 라우터는 명시적인 규칙을 따르며, 동일한 입력에 대해 동일한 경로를 생성합니다.
- 비결정론적 라우터는 동일한 입력에 대해 서로 다른 브랜치를 선택할 수 있습니다.
- 에이전트 라우터는 모델을 사용하여 요청을 해석하므로 선택이 달라질 수 있습니다.
라우터는 워크플로에서 정의한 경로 중에서 선택합니다. 고객 지원 예시에서는 에이전트를 사용하여 고객의 의도를 파악한 다음 고정된 규칙을 사용하여 환불 정책을 적용할 수 있습니다.
에이전트 라우터를 통해 의도 파악
에이전트는 고객의 메시지를 해석하고 요청을 분류할 수 있습니다. 특정 ADK 패턴에서는 에이전트가 구조화된 카테고리를 반환한 다음 small 함수를 통해 해당 Event(route=…)를 내보내 요청을 선택한 워크플로로 전송합니다.
모델은 의도를 파악하고, 그래프는 사용 가능한 대상을 정의합니다. 요청이 명확하지 않은 경우 워크플로에서 후속 질문을 할 수 있습니다. ADK의 라우팅 샘플에서 이 패턴을 확인할 수 있습니다.
이 의도 라우터는 환불 워크플로 앞에 배치됩니다. 선택한 주문 예시의 경우 '환불 요청' 버튼을 통해 이미 의도가 확정되었으므로 해당 워크플로로 바로 진입할 수 있습니다.
결정론적 라우터를 통해 환불 정책 적용
환불 워크플로 내에서 수행되는 세 가지 조회 작업을 통해 승인, 거부, 사람의 검토 요청이라는 또 다른 결정을 내리는 데 필요한 사실 정보를 제공합니다. 이번에는 정책에 명시적인 기준이 제시되어 있으므로 함수를 통해 경로를 선택할 수 있습니다.
route_refund는 레코드를 결합하고 AUTO_APPROVE, DENY, MANUAL_REVIEW 중 하나의 경로 이름으로 케이스를 반환합니다. 수동 검토는 자동 규칙을 모두 충족하지 않는 케이스를 처리합니다.
이는 결정론적 라우터입니다. 즉, 동일한 케이스 데이터는 동일한 결정을 생성하므로 모델을 호출하지 않고도 정책을 테스트할 수 있습니다.
두 라우터 모두 정의된 경로 중에서 선택합니다. 에이전트 라우터는 정적 그래프 내에 위치할 수 있습니다. 즉, 모델의 선택은 달라질 수 있지만 가능한 연결은 동일하게 유지됩니다.
자동 승인 또는 거부의 경우 다음 단계는 고객에게 결정 내용을 설명하는 것입니다. 각 경로에는 케이스 데이터를 바탕으로 답장을 작성하는 알림 에이전트가 지정됩니다. ADK는 Event(output=case, …)의 사전을 JSON 사용자 메시지 형식으로 해당 에이전트에 전달합니다.
이제 환불 워크플로에는 초기 조회부터 결정을 내리고 답장을 작성하기까지의 경로가 마련되어 있으며, 사람의 개입이 필요한 케이스를 위한 세 번째 브랜치도 포함되어 있습니다.
첫 번째 체인은 레코드를 가져오고, 조인하고, 정책을 적용합니다. 두 번째 체인은 라우터의 결정을 대상에 매핑합니다. 자동 결정은 알림 에이전트로 바로 전달되고, MANUAL_REVIEW는 다음에 정의할 사람의 검토를 위한 노드로 전달됩니다.


세 번째 브랜치를 실행하려면 함수 호출 이상의 것이 필요합니다. 검토자가 답변하는 데 몇 분 또는 며칠이 걸릴 수 있으므로 케이스가 대기 중인 상태에서 워크플로를 일시중지하고 사람이 결정을 내리면 계속 진행해야 합니다.
검토 노드는 대기 중인 요청을 기록하고 실행을 일시중지하는 RequestInput을 반환합니다. 이후 재개 시 rerun_on_resume=True를 설정하면 노드가 다시 실행되고, 이때 ctx.resume_inputs에서 답변을 확인할 수 있습니다.
응답 스키마는 라우팅할 approve 값과 전달할 메모를 노드에 제공합니다. 검토자가 거부하는 경우 알림 에이전트는 거부 사유를 해당 케이스와 함께 수신합니다. 검토 결정과 답장을 연결하는 에지가 하나 더 있습니다.
ADK는 두 가지 변형을 제공합니다. 위의 패턴은 단일 노드 패턴입니다. 즉, 노드가 다시 실행되고 request_input_rerun 샘플에서와 같이 ctx.resume_inputs를 읽습니다. 반면 request_input 샘플에서는 2노드 변형을 확인할 수 있습니다. 여기서는 하나의 노드가 RequestInput을 반환하면 검토자의 답변이 다음 노드의 node_input으로 도착하므로 해당 파일에서 ctx.resume_inputs를 찾을 수 없습니다. 아래에 링크된 컴패니언 스크립트에는 검토자의 답변을 실행 단계로 다시 전송하는 코드가 포함되어 있습니다.
Workflow 또한 노드입니다. 이 전체 환불 프로세스는 더 큰 규모의 고객 서비스 워크플로에서 하나의 단계로 통합될 수 있습니다.
케이스 배치에 동일한 단계 적용하기
이제 하나의 환불을 처리하는 프로세스가 마련되었습니다. 이미 수집된 레코드가 포함된 케이스 배치가 들어온다고 가정해 보겠습니다. 케이스마다 동일한 정책 검사가 필요하며 케이스 수는 배치마다 다릅니다. 동시 작업자를 사용하여 모든 항목에 하나의 노드를 적용할 수 있습니다.
ADK에서 parallel_worker=True를 설정하면 입력 목록의 항목당 노드가 한 번 실행되고 결과가 원래 순서대로 수집됩니다. 정책 함수를 다시 사용할 수 있습니다.
각 작업자는 하나의 케이스를 수신하고 collect_decisions는 결과를 목록으로 수신합니다. 별도의 JoinNode는 필요하지 않습니다. 이 플래그는 에이전트에서도 작동합니다. 예를 들어 각 케이스에 대한 설명을 작성할 수 있습니다. parallel_worker 샘플에서 두 가지 형식을 모두 확인할 수 있습니다.
여기서 수행되는 정책 계산은 규모가 작습니다. 동시 실행은 각 항목이 API 또는 모델 호출을 기다려야 하는 상황에서 더 유용하지만 입력과 결과가 이동하는 방식은 동일하게 유지됩니다.
배치 크기는 그래프를 변경하지 않고도 조정할 수 있습니다. 고정된 프로세스 내에서도 가변적인 작업량을 처리할 수 있습니다. 실행에 대한 자세한 내용은 동시 작업자 가이드를 참조하세요.
결과에 따라 다음 단계 진행하기
환불 프로세스에는 알려진 경로가 있습니다. 배치에 더 많은 케이스가 포함되어 있거나 검토자가 답변하는 데 더 오래 걸리는 경우에도 마찬가지입니다. 하지만 일부 작업은 조사를 진행하는 과정에서 명확해집니다.
분쟁의 대상이 된 환불 프로세스에서 두 번째 거래가 드러난다고 가정해 보겠습니다. 이를 확인하는 과정에서 추가 조사가 필요한 배송 관련 문제가 제기됩니다. 이제 각 결과를 통해 후속 작업을 만들 수 있습니다. 동적 노드는 이러한 결과를 검사하고 Python에서 다음 검사를 예약할 수 있습니다.
ADK는 다른 노드를 실행하고 그 결과를 기다리는 ctx.run_node를 제공합니다. 이것이 조정에 어떤 변화를 가져오는지 살펴보기 위해 먼저 기존 환불 흐름을 다음과 같이 표현해 보겠습니다.
asyncio.gather는 조회 작업을 동시에 실행합니다. Python은 결과를 결합하고 정책을 적용한 후 선택한 핸들러를 실행합니다. use_as_output=True를 설정하면 핸들러의 결과가 두 번 출력되지 않고 상위 노드의 출력으로 사용됩니다.
동적 버전에서는 사람의 검토를 위한 노드도 조정합니다. 즉, 답변이 도착하면 ctx.run_node를 통해 선택한 알림 에이전트를 호출합니다. 전체 동적 스크립트(refund_dynamic.py)에는 이 변형이 포함되어 있습니다.


외부 그래프에는 진입점만 있으면 됩니다.
정적 버전에서는 에지 목록에 브랜치와 대상이 표시됩니다. 이 버전에서는 refund_flow를 읽어 이를 확인합니다. 동적 노드 가이드에서 이 접근 방식을 다룹니다.
사람의 검토를 위한 일시중지는 여기에서도 계속 작동합니다. 답변이 도착하면 refund_flow가 처음부터 다시 실행되지만, 완료된 ctx.run_node 호출은 세션 기록에서 기록된 출력을 반환합니다. 직접 함수 호출과 달리 조회 작업은 반복되지 않습니다. 부차적인 효과를 하위 노드 내에서 처리하면 상위 노드가 재개될 때 완료된 호출의 결과를 재현할 수 있습니다. 각 하위 노드는 자체 trace 스팬을 가져오고 use_sub_branch=True를 설정하면 동시 실행 중인 하위 노드의 이벤트가 별도의 브랜치에 유지됩니다.
이 고정된 환불 프로세스의 경우 에지 목록을 쉽게 검사할 수 있습니다. Python 버전은 위에서 설명한 조사 로직을 추가할 수 있는 공간을 제공합니다. 즉, 결과를 검사하고, 후속 노드를 선택하고, 독립적인 검사를 동시에 실행할 수 있습니다. 모델은 조사할 대상을 제안할 수 있지만, 코드는 작업 범위를 제한합니다. 예를 들어 발견 사항당 3회의 후속 조치와 사람의 검토 전 2단계의 조사를 수행합니다.
루프 또한 정적 그래프 내에서 처리할 수 있습니다. 초안 작성 → 검사 → 수정으로 이어지는 고정된 프로세스에서는 조건부 역방향 에지와 수정 횟수를 제한하는 라우터를 사용할 수 있습니다. '정적'은 가능한 연결을 설명하므로 실제 경로와 반복 횟수는 다를 수 있습니다. 조건부 순환에 대해서는 그래프 가이드에서 다룹니다.
또한 정적 워크플로 내에 동적 노드를 배치할 수도 있습니다. 그러면 필요한 단계에 Python을 사용하면서 주변 프로세스를 계속 표시할 수 있습니다.
다음에 실행할 작업을 결정할 사람 선택하기
먼저 입력이 도착하기 전에 가능한 워크플로를 그려볼 수 있나요?와 같이 질문할 수 있습니다. 이때 분기, 루프, 반복 단계를 포함해야 합니다. 각 요청이 거치게 될 구체적인 경로를 미리 예측할 필요는 없습니다.
에지 목록은 연결 관계를 명확히 보여주는 경우 사용합니다. 결과가 추가 작업을 생성하거나 Python이 제어를 더 자연스럽게 표현하는 경우 동적 조정을 사용합니다. 규모가 작은 개방형 작업에는 하나의 에이전트와 도구만 필요할 수 있습니다.
환불 워크플로에서 그래프는 조회를 조정하고, 코드는 정책을 적용하고, 사람은 예외를 처리하고, 모델은 답장을 작성합니다. 그래프 엔지니어링은 각 구성 요소에 명확한 책임을 부여하고 프로세스의 작동 방식을 더 쉽게 파악할 수 있도록 합니다.
시작하기
-
읽기: 워크플로 가이드
-
탐색: 워크플로 샘플
-
빌드: 이러한 패턴을 마라톤 경기 당일 코치에 적용하는 실습 Codelab



