다중 에이전트 시스템

전문 에이전트를 팀으로 구성하여 정교한 애플리케이션을 구축하세요.

에이전트 계층 구조 개요

Rendering architecture…

구축할 내용

이 가이드에서는 코디네이터가 질의를 전문가에게 전달하는 고객 서비스 시스템을 만듭니다.

                        ┌─────────────────────┐
       User Query       │                     │
      ────────────────▶ │    COORDINATOR      │
                        │  "Route to expert"  │
                        └──────────┬──────────┘
                                   │
                   ┌───────────────┴───────────────┐
                   │                               │
                   ▼                               ▼
        ┌──────────────────┐            ┌──────────────────┐
        │  BILLING AGENT   │            │  SUPPORT AGENT   │
        │                  │            │                  │
        │  💰 Payments     │            │  🔧 Tech Issues  │
        │  📄 Invoices     │            │  🐛 Bug Reports  │
        │  💳 Subscriptions│            │  ❓ How-To       │
        └──────────────────┘            └──────────────────┘

핵심 개념:

  • 코디네이터 - 모든 요청을 받고, 누가 처리할지 결정합니다
  • 전문가 - 특정 도메인에서 뛰어난 역량을 발휘하는 전문 에이전트입니다
  • 전달 - 코디네이터에서 전문가로 원활하게 인계합니다

이식 가능한 TeamSpec

생성된 프로젝트, CLI, Studio 미리 보기 및 Enterprise 배포 환경에서 동일한 팀을 동일하게 실행해야 할 때 TeamSpec을 사용하세요. 사양은 직렬화 가능한 데이터이며, compile는 선언된 각 이름을 기존의 Agent 구현에 바인딩하고 실행 가능한 CompiledTeam 루트를 반환합니다. 새로운 원자적 에이전트 종류를 추가하지는 않습니다.

use adk_agent::{
    Agent, RelationshipKind, TeamMemberSpec, TeamRelationship, TeamSpec,
};
use std::sync::Arc;

let spec = TeamSpec {
    name: "support_team".into(),
    description: "Customer support routing".into(),
    coordinator: "coordinator".into(),
    members: vec![
        TeamMemberSpec::new("coordinator"),
        TeamMemberSpec::new("billing_agent"),
        TeamMemberSpec::new("research_agent"),
    ],
    relationships: vec![
        TeamRelationship::new(
            "coordinator", "billing_agent", RelationshipKind::Handoff,
        ),
        TeamRelationship::new(
            "coordinator", "research_agent", RelationshipKind::Delegate,
        ),
    ],
    policy: Default::default(),
};

let root = spec.compile(vec![
    Arc::new(coordinator) as Arc<dyn Agent>,
    Arc::new(billing_agent),
    Arc::new(research_agent),
])?;

크레이트를 직접 사용하려면 사양에 Delegate이 포함된 경우 adk-agent의 team-tools 기능을 활성화하세요. 상위 크레이트를 사용할 때는 tools(또는 standard 계층)을 활성화하세요. 핸드오프 전용 사양은 기본 adk-agent 기능 세트로 컴파일됩니다.

관계제어 동작하위 수준 메커니즘
Handoff호출자가 제어를 전달하고 재개하지 않음Runner transfer_to_agent
Delegate호출자가 멤버를 호출하고 결과를 받은 후 재개함AgentTool

대상은 각 멤버에 대해 정확하게 지정됩니다. 컴파일러는 중복 멤버와 엣지, 누락된 엔드포인트, 사이클, 도달할 수 없는 멤버, 잘못된 범위, 누락된 에이전트 바인딩 및 추가 바인딩을 거부합니다. 위임 엣지는 입력/출력 JSON 스키마, 컨텍스트 및 기록 프로젝션, 정확한 상태 키, 타임아웃 및 승인 요구 사항, 재시도/대체 동작, 서킷 브레이커를 전달할 수 있습니다. 핸드오프 엣지는 제한된 시작 재시도/대체를 지원하며, 위임 전용 필드는 조용히 무시하지 않고 거부됩니다. 대체 항목은 별도로 선언된 엣지여야 하며, 검증 과정에서 해당 항목의 권한 범위를 넓히지 않습니다.

팀 전체 정책은 핸드오프/위임 깊이, 동시성, 이벤트, 모델 및 도구 호출, 토큰, 비용, 관계 수와 총 실행 시간을 제한합니다. 종료 규칙은 에스컬레이션, 정확한 최종 작성자 또는 텍스트 마커를 기준으로 중지할 수 있습니다. 모든 이벤트는 __adk_team_execution_v1에 직렬화 가능한 실행 영수증을 포함하며, 여기에는 동결된 구성원 목록, 인과 관계가 있는 위임/핸드오프 엣지, 집계된 사용량 및 종료 상태가 포함됩니다. CompiledTeam::execution_snapshot을 사용해 이를 검사하고, 동일한 호출 ID로 재개하기 전에 restore_execution_snapshot을 사용합니다. 활성 핸드오프는 동결된 대상에서 재개됩니다. 일반적인 Agent 계약으로는 도구 호출이 멱등적인지 증명할 수 없으므로, 해결되지 않은 위임은 자동으로 재생되지 않으며 런타임은 대신 조치 가능한 오류를 반환합니다. 자체 체크포인트 메커니즘을 통해 해당 위임을 재개하거나 작업을 명시적으로 실패/재시작합니다.

재사용 가능한 아키텍처의 경우, TeamArchitectureTemplate는 supervisor, router 및 계층적 프리셋을 명시적인 TeamSpec 데이터로 낮춥니다. WorkflowArchitectureTemplate는 기존 워크플로 에이전트에서 순차, 병렬, fan-out/fan-in 및 제한된 검토 루프 프리셋을 컴파일합니다. 기능 기반 확인은 TeamAgentRegistry를 통해 사용할 수 있으며, 선택은 결정론적이고 상태, 버전, 다이제스트 및 신뢰 레이블을 요구할 수 있으며, 선택된 구성 목록은 실행 영수증에 고정됩니다.

실행 플레인 기능

컴파일은 소스 에이전트가 선언한 AgentCapabilities를 확인합니다. delegate 소스는 호출 범위의 런타임 도구를 사용해야 하고, handoff 소스는 전달을 내보내야 하며, 승인된 delegate는 정확한 호출 확인을 지원해야 합니다. LlmAgent 및 CodeActAgent는 지원되는 런타임 기능을 선언합니다. 사용자 지정 에이전트는 보수적인 기본값을 유지하며 capabilities()을 재정의할 수 있습니다.

팀은 표준 ADK 동작을 유지합니다. Runner 플러그인, 에이전트/모델/도구 콜백, 가드레일, 취소, 요청 식별자, 메모리, 아티팩트, 세션 및 이벤트 지속성은 여전히 기존 멤버 에이전트를 감쌉니다. compile_with_hooks은 팀, 멤버 및 정확한 관계 경계에 대해 순서가 지정된 비동기 TeamLifecycleHook 콜백을 추가합니다. 훅은 성공/실패를 관찰하거나 대기 중인 작업을 거부할 수 있지만 대상 범위를 확장하지는 않습니다. Runner는 검증된 handoff 직전에 루트의 비동기 govern_transfer 훅도 호출합니다.

네이티브 team.run, team.member.run 및 team.relationship.execute 트레이싱 스팬은 일반 adk-telemetry 익스포터를 통해 팀/멤버/엣지 식별자, 상태 및 관계 기간을 전달합니다. 런타임 실패에는 agent.team.policy_denied, agent.team.budget_exceeded 및 agent.team.resume_unsafe과 같은 안정적인 구조화 코드가 사용됩니다.

Delegate 계약은 상태 읽기(stateKeys)와 쓰기(stateWriteKeys)를 구분합니다. stateMerge: rejectConflicts은 위임 중 부모 값이 변경된 경우 자식 쓰기를 거부하며, artifactPrefixes은 아티팩트 이름을 제한합니다. 독립 실행형 AgentTool은 동일한 제어 기능을 제공합니다. 호환성을 위해 기본 동작은 여전히 최종 기록 우선(last-writer-wins)이며, Team 관계의 기본값은 충돌 거부입니다.

CompiledTeam::resume_plan는 영수증을 복원한 후 취할 수 있는 유일하게 안전한 다음 작업을 설명합니다. coordinator, handoff 대상, 체크포인트를 인식하는 delegate 또는 안전하지 않은 fail-closed 위임 중 하나입니다. validate_team_replay은 인과적 영수증 무결성을 검증하고, analyze_execution는 테스트 및 평가 파이프라인을 위해 provider가 없는 토폴로지 범위, 실패 및 최대 인과 깊이를 보고합니다.

BlackboardSpec는 관리되는 라운드 로빈 및 selector 기반 그룹 채팅을 다룹니다. 라운드와 transcript 투영을 제한하며, selector 전환에는 정확한 allowlist를 사용합니다. 기존 sub_agent 컴포지션은 하위 호환성을 위해 계속 지원되지만, 이 방식은 이식 가능한 팀 정의가 아니라 메모리 내 트리입니다.


빠른 시작

1. 프로젝트 생성

cargo new multi_agent_demo
cd multi_agent_demo

Cargo.toml에 종속성을 추가합니다:

[dependencies]
adk-rust = "2.1.0"
tokio = { version = "1", features = ["full"] }
dotenvy = "0.15"

API 키를 사용하여 .env을 생성합니다:

echo 'GOOGLE_API_KEY=your-api-key' > .env

2. 고객 서비스 예제

다음은 완전히 작동하는 예제입니다:

use adk_rust::prelude::*;
use adk_rust::Launcher;
use std::sync::Arc;

#[tokio::main]
async fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
    dotenvy::dotenv().ok();
    let api_key = std::env::var("GOOGLE_API_KEY")?;
    let model = Arc::new(GeminiModel::new(&api_key, "gemini-3.7-flash")?);

    // Specialist: Billing Agent
    let billing_agent = LlmAgentBuilder::new("billing_agent")
        .description("Handles billing questions: payments, invoices, subscriptions, refunds")
        .instruction("You are a billing specialist. Help customers with:\n\
                     - Invoice questions and payment history\n\
                     - Subscription plans and upgrades\n\
                     - Refund requests\n\
                     - Payment method updates\n\
                     Be professional and provide clear information about billing matters.")
        .model(model.clone())
        .build()?;

    // Specialist: Technical Support Agent
    let support_agent = LlmAgentBuilder::new("support_agent")
        .description("Handles technical support: bugs, errors, troubleshooting, how-to questions")
        .instruction("You are a technical support specialist. Help customers with:\n\
                     - Troubleshooting errors and bugs\n\
                     - How-to questions about using the product\n\
                     - Configuration and setup issues\n\
                     - Performance problems\n\
                     Be patient and provide step-by-step guidance.")
        .model(model.clone())
        .build()?;

    // Coordinator: Routes to appropriate specialist
    let coordinator = LlmAgentBuilder::new("coordinator")
        .description("Main customer service coordinator")
        .instruction("You are a customer service coordinator. Analyze each customer request:\n\n\
                     - For BILLING questions (payments, invoices, subscriptions, refunds):\n\
                       Transfer to billing_agent\n\n\
                     - For TECHNICAL questions (errors, bugs, how-to, troubleshooting):\n\
                       Transfer to support_agent\n\n\
                     - For GENERAL greetings or unclear requests:\n\
                       Respond yourself and ask clarifying questions\n\n\
                     When transferring, briefly acknowledge the customer and explain the handoff.")
        .model(model.clone())
        .sub_agent(Arc::new(billing_agent))
        .sub_agent(Arc::new(support_agent))
        .build()?;

    println!("🏢 Customer Service Center");
    println!("   Coordinator → Billing Agent | Support Agent");
    println!();

    Launcher::new(Arc::new(coordinator)).run().await?;
    Ok(())
}

예시 상호작용:

You: I have a question about my last invoice

[Agent: coordinator]
Assistant: I'll connect you with our billing specialist to help with your invoice question.

[Agent: billing_agent]
Assistant: Hello! I can help you with your invoice. What specific question do you have about your last invoice?

You: Why was I charged twice?

[Agent: billing_agent]
Assistant: I understand your concern about the duplicate charge. Let me help you investigate this...

다중 에이전트 전송 작동 방식

전체 개요

부모 에이전트에 하위 에이전트를 추가하면 LLM은 제어권을 handoff할 수 있게 됩니다:

                    ┌─────────────────────┐
    User Message    │                     │
   ─────────────────▶    COORDINATOR      │
                    │                     │
                    └──────────┬──────────┘
                               │
           "This is a billing question..."
                               │
              ┌────────────────┴────────────────┐
              │                                 │
              ▼                                 ▼
   ┌──────────────────┐              ┌──────────────────┐
   │  billing_agent   │              │  support_agent   │
   │  💰 Payments     │              │  🔧 Tech Issues  │
   │  📄 Invoices     │              │  🐛 Bug Reports  │
   └──────────────────┘              └──────────────────┘

단계별 전송 흐름

사용자가 청구 관련 질문을 했을 때 정확히 어떤 일이 발생하는지 설명합니다:

┌──────────────────────────────────────────────────────────────────────┐
│ STEP 1: User sends message                                           │
├──────────────────────────────────────────────────────────────────────┤
│                                                                      │
│   User: "Why was I charged twice on my invoice?"                     │
│                                                                      │
│                              ↓                                       │
│                                                                      │
│   ┌──────────────────────────────────────┐                          │
│   │         COORDINATOR AGENT            │                          │
│   │  Receives message first              │                          │
│   └──────────────────────────────────────┘                          │
│                                                                      │
└──────────────────────────────────────────────────────────────────────┘
                              ↓
┌──────────────────────────────────────────────────────────────────────┐
│ STEP 2: LLM analyzes and decides to transfer                         │
├──────────────────────────────────────────────────────────────────────┤
│                                                                      │
│   🧠 LLM thinks: "This is about an invoice charge..."                │
│                  "Invoice = billing topic..."                        │
│                  "I should transfer to billing_agent"                │
│                                                                      │
│   📞 LLM calls: transfer_to_agent(agent_name="billing_agent")        │
│                                                                      │
└──────────────────────────────────────────────────────────────────────┘
                              ↓
┌──────────────────────────────────────────────────────────────────────┐
│ STEP 3: Runner detects transfer and invokes target                   │
├──────────────────────────────────────────────────────────────────────┤
│                                                                      │
│   ┌─────────┐     transfer event      ┌─────────────────┐           │
│   │ Runner  │ ─────────────────────▶  │  billing_agent  │           │
│   └─────────┘   (same user message)   └─────────────────┘           │
│                                                                      │
│   • Runner finds "billing_agent" in agent tree                       │
│   • Creates new context with SAME user message                       │
│   • Invokes billing_agent immediately                                │
│                                                                      │
└──────────────────────────────────────────────────────────────────────┘
                              ↓
┌──────────────────────────────────────────────────────────────────────┐
│ STEP 4: Target agent responds                                        │
├──────────────────────────────────────────────────────────────────────┤
│                                                                      │
│   ┌─────────────────────────────────────────┐                       │
│   │           billing_agent responds        │                       │
│   │                                         │                       │
│   │  "I can help with your duplicate        │                       │
│   │   charge. Let me investigate..."        │                       │
│   └─────────────────────────────────────────┘                       │
│                                                                      │
│   ✅ User sees seamless response - no interruption!                  │
│                                                                      │
└──────────────────────────────────────────────────────────────────────┘

작동하게 만드는 요소

구성 요소역할
.sub_agent()상위 항목에 전문 에이전트를 등록
transfer_to_agent 도구하위 에이전트가 존재할 때 자동 주입
에이전트 설명LLM가 어떤 에이전트가 무엇을 처리할지 결정하도록 지원
실행기전환 이벤트를 감지하고 대상 에이전트를 호출
공유 세션전환 간 상태 및 기록 보존

하위 에이전트 추가 전후 비교

하위 에이전트 없음 - 하나의 에이전트가 모든 작업을 수행:

User ──▶ coordinator ──▶ Response (handles billing AND support)

하위 에이전트 사용 - 전문가가 각자의 영역을 담당:

User ──▶ coordinator ──▶ billing_agent ──▶ Response (billing expert)
                    ──▶ support_agent ──▶ Response (tech expert)

계층적 다중 에이전트 시스템

복잡한 시나리오에서는 다단계 계층 구조를 만들 수 있습니다. 각 에이전트가 자체 하위 에이전트를 가질 수 있어 트리 구조를 형성합니다.

시각화: 3단계 콘텐츠 팀

                    ┌─────────────────────┐
                    │  PROJECT MANAGER    │  ← Level 1: Top-level coordinator
                    │  "Manage projects"  │
                    └──────────┬──────────┘
                               │
                               ▼
                    ┌─────────────────────┐
                    │  CONTENT CREATOR    │  ← Level 2: Mid-level coordinator  
                    │  "Coordinate R&W"   │
                    └──────────┬──────────┘
                               │
              ┌────────────────┴────────────────┐
              │                                 │
              ▼                                 ▼
   ┌──────────────────┐              ┌──────────────────┐
   │   RESEARCHER     │              │     WRITER       │  ← Level 3: Specialists
   │                  │              │                  │
   │  📚 Gather facts │              │  ✍️ Write content │
   │  🔍 Analyze data │              │  📝 Polish text  │
   │  📊 Find sources │              │  🎨 Style & tone │
   └──────────────────┘              └──────────────────┘

요청이 하위로 전달되는 방식

User: "Create a blog post about electric vehicles"
                        │
                        ▼
┌─────────────────────────────────────────────────────────────┐
│  PROJECT MANAGER: "This is a content task"                  │
│  → transfers to content_creator                             │
└─────────────────────────────────────────────────────────────┘
                        │
                        ▼
┌─────────────────────────────────────────────────────────────┐
│  CONTENT CREATOR: "Need research first, then writing"       │
│  → transfers to researcher                                  │
└─────────────────────────────────────────────────────────────┘
                        │
                        ▼
┌─────────────────────────────────────────────────────────────┐
│  RESEARCHER: "Here's what I found about EVs..."             │
│  → provides research summary                                │
└─────────────────────────────────────────────────────────────┘

전체 예제 코드

use adk_rust::prelude::*;
use adk_rust::Launcher;
use std::sync::Arc;

#[tokio::main]
async fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
    dotenvy::dotenv().ok();
    let api_key = std::env::var("GOOGLE_API_KEY")?;
    let model = Arc::new(GeminiModel::new(&api_key, "gemini-3.7-flash")?);

    // Level 3: Leaf specialists
    let researcher = LlmAgentBuilder::new("researcher")
        .description("Researches topics and gathers comprehensive information")
        .instruction("You are a research specialist. When asked to research a topic:\n\
                     - Gather key facts and data\n\
                     - Identify main themes and subtopics\n\
                     - Note important sources or references\n\
                     Provide thorough, well-organized research summaries.")
        .model(model.clone())
        .build()?;

    let writer = LlmAgentBuilder::new("writer")
        .description("Writes polished content based on research")
        .instruction("You are a content writer. When asked to write:\n\
                     - Create engaging, clear content\n\
                     - Use appropriate tone for the audience\n\
                     - Structure content logically\n\
                     - Polish for grammar and style\n\
                     Produce professional, publication-ready content.")
        .model(model.clone())
        .build()?;

    // Level 2: Content coordinator
    let content_creator = LlmAgentBuilder::new("content_creator")
        .description("Coordinates content creation by delegating research and writing")
        .instruction("You are a content creation lead. For content requests:\n\n\
                     - If RESEARCH is needed: Transfer to researcher\n\
                     - If WRITING is needed: Transfer to writer\n\
                     - For PLANNING or overview: Handle yourself\n\n\
                     Coordinate between research and writing phases.")
        .model(model.clone())
        .sub_agent(Arc::new(researcher))
        .sub_agent(Arc::new(writer))
        .build()?;

    // Level 1: Top-level manager
    let project_manager = LlmAgentBuilder::new("project_manager")
        .description("Manages projects and coordinates with content team")
        .instruction("You are a project manager. For incoming requests:\n\n\
                     - For CONTENT creation tasks: Transfer to content_creator\n\
                     - For PROJECT STATUS or general questions: Handle yourself\n\n\
                     Keep track of overall project goals and deadlines.")
        .model(model.clone())
        .sub_agent(Arc::new(content_creator))
        .build()?;

    println!("📊 Hierarchical Multi-Agent System");
    println!();
    println!("   project_manager");
    println!("       └── content_creator");
    println!("               ├── researcher");
    println!("               └── writer");
    println!();

    Launcher::new(Arc::new(project_manager)).run().await?;
    Ok(())
}

에이전트 계층 구조:

project_manager
└── content_creator
    ├── researcher
    └── writer

예시 프롬프트:

  • "의료 분야의 AI에 대한 블로그 게시물 작성" → PM → 콘텐츠 작성자 → 작가
  • "전기 자동차 조사" → PM → 콘텐츠 작성자 → 연구원

하위 에이전트 구성

sub_agent() 빌더 메서드를 사용하여 모든 LlmAgent에 하위 에이전트를 추가합니다.

let parent = LlmAgentBuilder::new("parent")
    .description("Coordinates specialized tasks")
    .instruction("Route requests to appropriate specialists.")
    .model(model.clone())
    .sub_agent(Arc::new(specialist_a))
    .sub_agent(Arc::new(specialist_b))
    .build()?;

핵심 사항:

  • 각 에이전트는 여러 하위 에이전트를 가질 수 있습니다.
  • 하위 에이전트는 자체 하위 에이전트를 가질 수 있습니다(다단계 계층 구조).
  • 에이전트 이름은 계층 구조 내에서 고유해야 합니다.
  • 설명은 LLM가 어느 에이전트로 전환할지 결정하는 데 도움이 됩니다.

효과적인 전환 지침 작성

에이전트를 성공적으로 전환하려면 명확한 지침과 설명을 제공하세요.

상위 에이전트 지침

let coordinator = LlmAgentBuilder::new("coordinator")
    .description("Main customer service coordinator")
    .instruction("You are a customer service coordinator. Analyze each request:\n\n\
                 - For BILLING questions (payments, invoices, subscriptions):\n\
                   Transfer to billing_agent\n\n\
                 - For TECHNICAL questions (errors, bugs, troubleshooting):\n\
                   Transfer to support_agent\n\n\
                 - For GENERAL greetings or unclear requests:\n\
                   Respond yourself and ask clarifying questions")
    .model(model.clone())
    .sub_agent(Arc::new(billing_agent))
    .sub_agent(Arc::new(support_agent))
    .build()?;

하위 에이전트 설명

let billing_agent = LlmAgentBuilder::new("billing_agent")
    .description("Handles billing questions: payments, invoices, subscriptions, refunds")
    .instruction("You are a billing specialist. Help with payment and subscription issues.")
    .model(model.clone())
    .build()?;

let support_agent = LlmAgentBuilder::new("support_agent")
    .description("Handles technical support: bugs, errors, troubleshooting, how-to questions")
    .instruction("You are a technical support specialist. Provide step-by-step guidance.")
    .model(model.clone())
    .build()?;

권장 사항:

  • 목적을 명확히 나타내는 설명적인 에이전트 이름을 사용하세요.
  • 상세한 설명을 작성하세요. LLM는 이를 사용하여 전환을 결정합니다.
  • 사용자가 요청할 가능성이 높은 내용과 일치하는 구체적인 키워드를 설명에 포함하세요.
  • 상위 에이전트 지침에 명확한 위임 규칙을 제시하세요.
  • 에이전트 설명 전체에서 일관된 용어를 사용하세요.

다중 에이전트 시스템 테스트

예제 실행

cargo run --manifest-path examples/tier_examples/enterprise/Cargo.toml --bin 14-enterprise-multi-agent

예시 테스트 프롬프트

고객 서비스:

  • "지난 청구서에 대해 질문이 있습니다" → billing_agent로 라우팅해야 함
  • "앱이 계속 충돌합니다" → support_agent로 라우팅해야 함
  • "요금제를 어떻게 업그레이드하나요?" → billing_agent로 라우팅해야 함
  • "안녕하세요, 도움이 필요합니다" → уточ정을 위해 coordinator에 계속 남아 있어야 함

계층 구조:

  • "의료 분야의 AI에 대한 블로그 게시물을 작성해 주세요" → PM → 콘텐츠 제작자 → 작가
  • "전기 자동차의 역사를 조사해 주세요" → PM → 콘텐츠 제작자 → 연구자
  • "현재 프로젝트의 상태는 어떤가요?" → project_manager에 계속 남아 있어야 함

전송 문제 디버깅

전송이 예상대로 작동하지 않는 경우:

  1. 에이전트 이름 확인 - 전송 호출에서 정확히 일치해야 함
  2. 설명 검토 - 더 구체적이고 키워드가 풍부하도록 작성
  3. 지침 명확화 - 언제 전송해야 하는지 명시적으로 작성
  4. 경계 사례 테스트 - 라우팅 동작을 확인하기 위해 모호한 요청을 시도
  5. 전송 표시 확인 - [Agent: name]은 어떤 에이전트가 응답하고 있는지 보여 줌

전역 지침

기본 사용법

let agent = LlmAgentBuilder::new("assistant")
    .description("A helpful assistant")
    .global_instruction(
        "You are a professional assistant for Acme Corp. \
         Always maintain a friendly but professional tone. \
         Our company values are: customer-first, innovation, and integrity."
    )
    .instruction("Help users with their questions and tasks.")
    .model(model.clone())
    .build()?;

전역 지침과 에이전트 지침

  • 전역 지침: 계층 구조의 모든 에이전트에 적용되며, 전반적인 성격과 맥락을 설정함
  • 에이전트 지침: 각 에이전트에만 적용되며, 해당 에이전트의 특정 역할과 동작을 정의함

두 지침 모두 대화 기록에 포함되며, 전역 지침이 먼저 나타남.

동적 전역 지침

고급 시나리오에서는 지침을 동적으로 계산하는 전역 지침 제공자를 사용할 수 있음:

use adk_core::GlobalInstructionProvider;

let provider: GlobalInstructionProvider = Arc::new(|ctx| {
    Box::pin(async move {
        // Access context information
        let user_id = ctx.user_id();
        
        // Compute dynamic instruction
        let instruction = format!(
            "You are assisting user {}. Tailor your responses to their preferences.",
            user_id
        );
        
        Ok(instruction)
    })
});

let agent = LlmAgentBuilder::new("assistant")
    .description("A personalized assistant")
    .global_instruction_provider(provider)
    .model(model.clone())
    .build()?;

상태 변수 주입

전역 지침과 에이전트 지침 모두 {variable} 구문을 사용한 상태 변수 주입을 지원함:

// Set state in a previous agent or tool
// state["company_name"] = "Acme Corp"
// state["user_role"] = "manager"

let agent = LlmAgentBuilder::new("assistant")
    .global_instruction(
        "You are an assistant for {company_name}. \
         The user is a {user_role}."
    )
    .instruction("Help with {user_role}-level tasks.")
    .model(model.clone())
    .build()?;

프레임워크는 세션 상태의 값을 지침 템플릿에 자동으로 주입함.

일반적인 다중 에이전트 패턴

코디네이터/디스패처 패턴

중앙 에이전트가 요청을 전문 하위 에이전트로 라우팅합니다:

let billing = LlmAgentBuilder::new("billing")
    .description("Handles billing and payment questions")
    .model(model.clone())
    .build()?;

let support = LlmAgentBuilder::new("support")
    .description("Provides technical support")
    .model(model.clone())
    .build()?;

let coordinator = LlmAgentBuilder::new("coordinator")
    .instruction("Route requests to billing or support agents as appropriate.")
    .sub_agent(Arc::new(billing))
    .sub_agent(Arc::new(support))
    .model(model.clone())
    .build()?;

예시 대화:

User: I have a question about my last invoice

[Agent: coordinator]
Assistant: I'll connect you with our billing specialist.
🔄 [Transfer requested to: billing]

[Agent: billing]
Assistant: Hello! I can help you with your invoice. 
What specific question do you have?

User: Why was I charged twice?

[Agent: billing]
Assistant: Let me investigate that duplicate charge for you...

핵심 사항:

  • 코디네이터가 요청을 분석하고 청구 에이전트로 전달합니다
  • 청구 에이전트가 동일한 턴에서 즉시 응답합니다
  • 이후 메시지는 청구 에이전트와 계속 진행됩니다
  • 전달 표시기(🔄)는 핸드오프가 발생하는 시점을 보여 줍니다

계층적 작업 분해

복잡한 작업을 세분화하기 위한 다단계 계층 구조:

// Low-level specialists
let researcher = LlmAgentBuilder::new("researcher")
    .description("Researches topics and gathers information")
    .model(model.clone())
    .build()?;

let writer = LlmAgentBuilder::new("writer")
    .description("Writes content based on research")
    .model(model.clone())
    .build()?;

// Mid-level coordinator
let content_creator = LlmAgentBuilder::new("content_creator")
    .description("Creates content by coordinating research and writing")
    .sub_agent(Arc::new(researcher))
    .sub_agent(Arc::new(writer))
    .model(model.clone())
    .build()?;

// Top-level manager
let project_manager = LlmAgentBuilder::new("project_manager")
    .description("Manages content creation projects")
    .sub_agent(Arc::new(content_creator))
    .model(model.clone())
    .build()?;

워크플로 에이전트와 결합

다중 에이전트 시스템은 워크플로 에이전트(Sequential, Parallel, Loop)와 함께 사용할 때 효과적입니다:

use adk_agent::workflow::{SequentialAgent, ParallelAgent};

// Create specialized agents
let validator = LlmAgentBuilder::new("validator")
    .instruction("Validate the input data.")
    .output_key("validation_result")
    .model(model.clone())
    .build()?;

let processor = LlmAgentBuilder::new("processor")
    .instruction("Process data if {validation_result} is valid.")
    .output_key("processed_data")
    .model(model.clone())
    .build()?;

// Combine in a sequential workflow
let pipeline = SequentialAgent::new(
    "validation_pipeline",
    vec![Arc::new(validator), Arc::new(processor)]
);

// Use the pipeline as a sub-agent
let coordinator = LlmAgentBuilder::new("coordinator")
    .description("Coordinates data processing")
    .sub_agent(Arc::new(pipeline))
    .model(model.clone())
    .build()?;

에이전트 간 통신

계층 구조의 에이전트는 공유 세션 상태를 통해 통신합니다:

// Agent A saves data to state
let agent_a = LlmAgentBuilder::new("agent_a")
    .instruction("Analyze the topic and save key points.")
    .output_key("key_points")  // Automatically saves output to state
    .model(model.clone())
    .build()?;

// Agent B reads data from state
let agent_b = LlmAgentBuilder::new("agent_b")
    .instruction("Expand on the key points: {key_points}")
    .model(model.clone())
    .build()?;

output_key 구성은 에이전트의 최종 응답을 세션 상태에 자동으로 저장하여 후속 에이전트가 사용할 수 있도록 합니다.

AgentTool 상태 및 아티팩트 전달

AgentTool를 사용하여 에이전트를 도구로 래핑하면 하위 에이전트의 상태 변경 및 아티팩트가 상위 컨텍스트로 자동 전달됩니다:

use adk_tool::AgentTool;

// Create a sub-agent that modifies state
let data_processor = LlmAgentBuilder::new("data_processor")
    .instruction("Process the data and save results.")
    .output_key("processed_data")
    .model(model.clone())
    .build()?;

// Wrap as a tool - state_delta and artifact_delta are forwarded
let processor_tool = AgentTool::new(Arc::new(data_processor));

// Parent agent can use the tool and see state changes
let coordinator = LlmAgentBuilder::new("coordinator")
    .instruction("Use the data_processor tool, then access {processed_data}.")
    .model(model.clone())
    .tool(Arc::new(processor_tool))
    .build()?;

AgentTool는 내부적으로 하위 에이전트를 비스트리밍 모드(StreamingMode::None)로 실행하므로, 하위 에이전트는 상위 에이전트에 반환하기 전에 전체 응답을 누적합니다. 이를 통해 부분 스트리밍 청크로 인해 빈 결과가 생성되는 문제를 방지할 수 있습니다.

이를 통해 AgentTool 패턴을 사용할 때 상위 에이전트와 하위 에이전트 간에 원활한 데이터 흐름이 가능해집니다.

다중 에이전트 시스템 실행

런처 사용

Launcher는 다중 에이전트 시스템을 실행하고 테스트하는 간편한 방법을 제공합니다:

use adk_rust::Launcher;

let coordinator = /* your multi-agent setup */;

Launcher::new(Arc::new(coordinator))
    .run()
    .await?;

실행 모드:

# Interactive console mode
cargo run --manifest-path examples/tier_examples/enterprise/Cargo.toml --bin 14-enterprise-multi-agent

# Use a generated API project when you need HTTP serving
cargo adk new multi_agent_api --template api

기능:

  • 에이전트 표시기: 응답 중인 에이전트를 표시합니다 [Agent: coordinator]
  • 전환 시각화: 전환 이벤트를 표시합니다 🔄 [Transfer requested to: billing_agent]
  • 원활한 핸드오프: 전환 직후 대상 에이전트가 즉시 응답합니다
  • 대화 기록: 에이전트 전환 전반에서 컨텍스트를 유지합니다

전환 테스트

멀티 에이전트 시스템이 올바르게 작동하는지 확인하려면 다음을 수행합니다.

  1. 에이전트가 응답할 때 에이전트 이름이 대괄호 안에 표시되는지 확인합니다
  2. 에이전트가 핸드오프할 때 전환 표시기(🔄)가 나타나는지 확인합니다
  3. 다시 프롬프트를 입력하지 않아도 대상 에이전트가 즉시 응답하는지 확인합니다
  4. 올바른 라우팅을 보장하기 위해 다양한 요청 유형을 테스트합니다
  5. 존재하지 않는 에이전트로 전환하는 경우와 같은 경계 사례를 확인합니다

전환 문제 디버깅

전환이 작동하지 않는 경우:

  • .sub_agent()을 통해 하위 에이전트가 추가되었는지 확인합니다
  • 에이전트 설명을 확인합니다. LLM은 전환을 결정할 때 이 설명을 사용합니다
  • 지침을 검토합니다. 상위 에이전트에는 전환 시점이 명시되어 있어야 합니다
  • 에이전트 이름을 확인합니다. 전환 호출에서 이름이 정확히 일치해야 합니다
  • 이벤트 스트림에서 전환 작업을 확인할 수 있도록 로깅을 활성화합니다

모범 사례

  1. 명확한 설명: LLM이 올바른 전환 결정을 내릴 수 있도록 설명적인 에이전트 이름과 설명을 작성합니다
  2. 구체적인 지침: 각 에이전트에 해당 역할에 맞는 명확하고 집중된 지침을 제공합니다
  3. 전역 지침 사용: 모든 에이전트에서 일관된 성격과 컨텍스트를 설정합니다
  4. 상태 관리: 에이전트 간 통신에 output_key과 상태 변수를 사용합니다
  5. 계층 깊이 제한: 유지 관리성을 높이기 위해 계층을 얕게(2~3개 수준) 유지합니다
  6. 전환 로직 테스트: 다양한 요청에 대해 에이전트가 올바른 하위 에이전트로 전환하는지 확인합니다

이전: ← 워크플로 에이전트 | 다음: 그래프 에이전트 →