워크플로우 에이전트
워크플로우 Agent는 예측 가능한 패턴(순차 파이프라인, 병렬 실행 또는 반복 루프)으로 여러 Agent를 조율합니다. AI 추론을 사용하는 LlmAgent와 달리, 워크플로우 Agent는 결정론적 실행 경로를 따릅니다.
빠른 시작
새 프로젝트 생성:
cargo new workflow_demo
cd workflow_demo
Cargo.toml에 의존성을 추가하세요:
[dependencies]
adk-rust = "0.2.0"
tokio = { version = "1.40", features = ["full"] }
dotenvy = "0.15"
.env를 생성하세요:
echo 'GOOGLE_API_KEY=your-api-key' > .env
SequentialAgent
SequentialAgent는 서브 에이전트들을 순차적으로 실행합니다. 각 에이전트는 이전 에이전트들로부터 누적된 대화 기록을 참조합니다.
사용 시기
- 출력이 다음 단계로 전달되는 다단계 파이프라인
- 연구 → 분석 → 요약 워크플로우
- 데이터 변환 체인
전체 예시
src/main.rs를 다음으로 바꿉니다:
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-2.5-flash")?);
// Step 1: Research agent gathers information
let researcher = LlmAgentBuilder::new("researcher")
.instruction("Research the given topic. List 3-5 key facts or points. \
Be factual and concise.")
.model(model.clone())
.output_key("research") // Saves output to state
.build()?;
// Step 2: Analyzer agent identifies patterns
let analyzer = LlmAgentBuilder::new("analyzer")
.instruction("Based on the research above, identify 2-3 key insights \
or patterns. What's the bigger picture?")
.model(model.clone())
.output_key("analysis")
.build()?;
// Step 3: Summarizer creates final output
let summarizer = LlmAgentBuilder::new("summarizer")
.instruction("Create a brief executive summary combining the research \
and analysis. Keep it under 100 words.")
.model(model.clone())
.build()?;
// Create the sequential pipeline
let pipeline = SequentialAgent::new(
"research_pipeline",
vec![Arc::new(researcher), Arc::new(analyzer), Arc::new(summarizer)],
).with_description("Research → Analyze → Summarize");
println!("📋 Sequential Pipeline: Research → Analyze → Summarize");
println!();
Launcher::new(Arc::new(pipeline)).run().await?;
Ok(())
}
실행:
cargo run
예시 상호작용
You: Tell me about Rust programming language
🔄 [researcher] Researching...
Here are key facts about Rust:
1. Systems programming language created at Mozilla in 2010
2. Memory safety without garbage collection via ownership system
3. Zero-cost abstractions and minimal runtime
4. Voted "most loved language" on Stack Overflow for 7 years
5. Used by Firefox, Discord, Dropbox, and Linux kernel
🔄 [analyzer] Analyzing...
Key insights:
1. Rust solves the memory safety vs performance tradeoff
2. Strong developer satisfaction drives rapid adoption
3. Trust from major tech companies validates production-readiness
🔄 [summarizer] Summarizing...
Rust is a systems language that achieves memory safety without garbage
collection through its ownership model. Created at Mozilla in 2010, it's
been rated the most loved language for 7 consecutive years. Major companies
like Discord and Linux kernel adopt it for its zero-cost abstractions
and performance guarantees.
작동 방식
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 연구원 │ → │ 분석가 │ → │ 요약자 │
│ (1단계) │ │ (2단계) │ │ (3단계) │
└─────────────┘ └─────────────┘ └─────────────┘
↓ ↓ ↓
"주요 사실..." "통찰력..." "요약 보고서"
- 사용자 메시지가 첫 번째 Agent(연구원)에게 전달됩니다.
- 연구원의 응답이 기록에 추가됩니다.
- 분석가는 사용자 메시지 + 연구원의 응답을 봅니다.
- 요약자는 사용자 메시지 + 연구원 + 분석가의 응답을 봅니다.
- 마지막 Agent가 완료되면 파이프라인이 완료됩니다.
ParallelAgent
ParallelAgent는 모든 서브 에이전트를 동시에 실행합니다. 각 agent는 동일한 입력을 받아 독립적으로 작동합니다.
사용 시기
- 동일한 주제에 대한 여러 관점
- 팬아웃 처리 (동일한 입력, 다른 분석)
- 속도가 중요한 다중 작업 시나리오
전체 예시
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-2.5-flash")?);
// Three analysts with DISTINCT personas (important for parallel execution)
let technical = LlmAgentBuilder::new("technical_analyst")
.instruction("You are a senior software architect. \
FOCUS ONLY ON: code quality, system architecture, scalability, \
security vulnerabilities, and tech stack choices. \
Start your response with '🔧 TECHNICAL:' and give 2-3 bullet points.")
.model(model.clone())
.build()?;
let business = LlmAgentBuilder::new("business_analyst")
.instruction("You are a business strategist and MBA graduate. \
FOCUS ONLY ON: market opportunity, revenue model, competition, \
cost structure, and go-to-market strategy. \
Start your response with '💼 BUSINESS:' and give 2-3 bullet points.")
.model(model.clone())
.build()?;
let user_exp = LlmAgentBuilder::new("ux_analyst")
.instruction("You are a UX researcher and designer. \
FOCUS ONLY ON: user journey, accessibility, pain points, \
visual design, and user satisfaction metrics. \
Start your response with '🎨 UX:' and give 2-3 bullet points.")
.model(model.clone())
.build()?;
// Create parallel agent
let multi_analyst = ParallelAgent::new(
"multi_perspective",
vec![Arc::new(technical), Arc::new(business), Arc::new(user_exp)],
).with_description("Technical + Business + UX analysis in parallel");
println!("⚡ Parallel Analysis: Technical | Business | UX");
println!(" (All three run simultaneously!)");
println!();
Launcher::new(Arc::new(multi_analyst)).run().await?;
Ok(())
}
💡 팁:
parallel agent지침을 고유한 페르소나, 초점 영역 및 응답 접두사로 매우 명확하게 구분하세요. 이를 통해 각 agent가 고유한 출력을 생성할 수 있습니다.
상호 작용 예시
You: Evaluate a mobile banking app
🔧 TECHNICAL:
• Requires robust API security: OAuth 2.0, certificate pinning, encrypted storage
• Offline mode with sync requires complex state management and conflict resolution
• Biometric auth integration varies significantly across iOS/Android platforms
💼 BUSINESS:
• Highly competitive market - need unique differentiator (neobanks, traditional banks)
• Revenue model: interchange fees, premium tiers, or lending products cross-sell
• Regulatory compliance costs significant: PCI-DSS, regional banking laws, KYC/AML
🎨 UX:
• Critical: fast task completion - check balance must be < 3 seconds
• Accessibility essential: screen reader support, high contrast mode, large touch targets
• Trust indicators important: security badges, familiar banking patterns
작동 방식
┌─────────────────┐
│ User Message │
└────────┬────────┘
┌───────────────────┼───────────────────┐
↓ ↓ ↓
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Technical │ │ Business │ │ UX │
│ Analyst │ │ Analyst │ │ Analyst │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
↓ ↓ ↓
(response 1) (response 2) (response 3)
모든 agent는 동시에 시작되며 완료되면 결과가 스트리밍됩니다.
LoopAgent
LoopAgent는 종료 조건이 충족되거나 최대 반복 횟수에 도달할 때까지 하위 에이전트를 반복적으로 실행합니다.
사용 시기
- 반복적인 개선 (초안 → 비평 → 개선 → 반복)
- 개선을 포함한 재시도 로직
- 여러 단계를 거쳐야 하는 품질 게이트
ExitLoopTool
루프를 조기에 종료하려면 에이전트에 ExitLoopTool을 부여하십시오. 이 도구가 호출되면 루프를 중지하라는 신호를 보냅니다.
전체 예시
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-2.5-flash")?);
// Critic agent evaluates content
let critic = LlmAgentBuilder::new("critic")
.instruction("Review the content for quality. Score it 1-10 and list \
specific improvements needed. Be constructive but critical.")
.model(model.clone())
.build()?;
// Refiner agent improves based on critique
let refiner = LlmAgentBuilder::new("refiner")
.instruction("Apply the critique to improve the content. \
If the score is 8 or higher, call exit_loop to finish. \
Otherwise, provide an improved version.")
.model(model.clone())
.tool(Arc::new(ExitLoopTool::new())) // Can exit the loop
.build()?;
// Create inner sequential: critic → refiner
let critique_refine = SequentialAgent::new(
"critique_refine_step",
vec![Arc::new(critic), Arc::new(refiner)],
);
// Wrap in loop with max 3 iterations
let iterative_improver = LoopAgent::new(
"iterative_improver",
vec![Arc::new(critique_refine)],
).with_max_iterations(3)
.with_description("Critique-refine loop (max 3 passes)");
println!("🔄 Iterative Improvement Loop");
println!(" critic → refiner → repeat (max 3x or until quality >= 8)");
println!();
Launcher::new(Arc::new(iterative_improver)).run().await?;
Ok(())
}
상호 작용 예시
You: Write a tagline for a coffee shop
🔄 Iteration 1
[critic] Score: 5/10. "Good coffee here" is too generic. Needs:
- Unique value proposition
- Emotional connection
- Memorable phrasing
[refiner] Improved: "Where every cup tells a story"
🔄 Iteration 2
[critic] Score: 7/10. Better! But could be stronger:
- More action-oriented
- Hint at the experience
[refiner] Improved: "Brew your perfect moment"
🔄 Iteration 3
[critic] Score: 8/10. Strong, action-oriented, experiential.
Minor: could be more distinctive.
[refiner] Score is 8+, quality threshold met!
[exit_loop called]
Final: "Brew your perfect moment"
작동 방식
┌──────────────────────────────────────────┐
│ LoopAgent │
│ ┌────────────────────────────────────┐ │
│ │ SequentialAgent │ │
│ │ ┌──────────┐ ┌──────────────┐ │ │
→ │ │ │ Critic │ → │ Refiner │ │ │ →
│ │ │ (review) │ │ (improve or │ │ │
│ │ └──────────┘ │ exit_loop) │ │ │
│ │ └──────────────┘ │ │
│ └────────────────────────────────────┘ │
│ ↑_____________↓ │
│ repeat until exit │
└──────────────────────────────────────────┘
ConditionalAgent (규칙 기반)
ConditionalAgent는 동기적이고 규칙 기반의 조건에 따라 실행을 분기합니다. 이는 A/B 테스트 또는 환경 기반 라우팅과 같은 확정적 라우팅에 사용됩니다.
ConditionalAgent::new("router", |ctx| ctx.session().state().get("premium")..., premium_agent)
.with_else(basic_agent)
참고: LLM 기반 지능형 라우팅의 경우 대신
LlmConditionalAgent를 사용하세요.
LlmConditionalAgent (LLM 기반)
LlmConditionalAgent는 사용자 입력을 LLM으로 분류하고 적절한 하위 에이전트로 라우팅합니다. 이는 라우팅 결정에 내용 이해가 필요한 지능형 라우팅에 이상적입니다.
사용 시기
- 의도 분류 - 사용자가 무엇을 묻는지에 따라 라우팅
- 다중 라우팅 - 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-2.5-flash")?);
// Create specialist agents
let tech_agent: Arc<dyn Agent> = Arc::new(
LlmAgentBuilder::new("tech_expert")
.instruction("You are a senior software engineer. Be precise and technical.")
.model(model.clone())
.build()?
);
let general_agent: Arc<dyn Agent> = Arc::new(
LlmAgentBuilder::new("general_helper")
.instruction("You are a friendly assistant. Explain simply, use analogies.")
.model(model.clone())
.build()?
);
let creative_agent: Arc<dyn Agent> = Arc::new(
LlmAgentBuilder::new("creative_writer")
.instruction("You are a creative writer. Be imaginative and expressive.")
.model(model.clone())
.build()?
);
// LLM classifies the query and routes accordingly
let router = LlmConditionalAgent::new("smart_router", model.clone())
.instruction("Classify the user's question as exactly ONE of: \
'technical' (coding, debugging, architecture), \
'general' (facts, knowledge, how-to), \
'creative' (writing, stories, brainstorming). \
Respond with ONLY the category name.")
.route("technical", tech_agent)
.route("general", general_agent.clone())
.route("creative", creative_agent)
.default_route(general_agent)
.build()?;
println!("🧠 LLM-Powered Intelligent Router");
Launcher::new(Arc::new(router)).run().await?;
Ok(())
}
예시 상호작용
You: Rust에서 빌림 오류를 어떻게 수정하나요?
[다음으로 라우팅: technical]
[에이전트: tech_expert]
빌림 오류는 Rust의 소유권 규칙이 위반될 때 발생합니다...
You: 프랑스의 수도는 어디인가요?
[다음으로 라우팅: general]
[에이전트: general_helper]
프랑스의 수도는 파리입니다! 아름다운 도시이죠...
You: 달에 대한 하이쿠를 써주세요
[다음으로 라우팅: creative]
[에이전트: creative_writer]
저 위 은빛 구슬,
조용한 물결에 그림자 춤추고—
밤은 비밀을 속삭이네.
작동 방식
┌─────────────────┐
│ 사용자 메시지 │
└────────┬────────┘
↓
┌─────────────────┐
│ LLM 분류 │ "technical" / "general" / "creative"
│ (smart_router) │
└────────┬────────┘
↓
┌────┴────┬──────────┐
↓ ↓ ↓
┌───────┐ ┌───────┐ ┌─────────┐
│ tech │ │general│ │creative │
│expert │ │helper │ │ writer │
└───────┘ └───────┘ └─────────┘
워크플로우 Agent 결합하기
워크플로우 Agent는 복잡한 패턴을 위해 중첩될 수 있습니다.
Sequential + Parallel + Loop
use adk_rust::prelude::*;
use std::sync::Arc;
// 1. 여러 관점에서의 Parallel 분석
let parallel_analysis = ParallelAgent::new(
"multi_analysis",
vec![Arc::new(tech_analyst), Arc::new(biz_analyst)],
);
// 2. Parallel 결과 종합하기
let synthesizer = LlmAgentBuilder::new("synthesizer")
.instruction("Combine all analyses into a unified recommendation.")
.model(model.clone())
.build()?;
// 3. 품질 루프: 비판 및 개선
let quality_loop = LoopAgent::new(
"quality_check",
vec![Arc::new(critic), Arc::new(refiner)],
).with_max_iterations(2);
// 최종 파이프라인: parallel → synthesize → quality loop
let full_pipeline = SequentialAgent::new(
"full_analysis_pipeline",
vec![
Arc::new(parallel_analysis),
Arc::new(synthesizer),
Arc::new(quality_loop),
],
);
워크플로우 실행 추적하기
워크플로우 내부에서 무슨 일이 일어나고 있는지 확인하려면 추적을 활성화하세요:
use adk_rust::prelude::*;
use adk_rust::runner::{Runner, RunnerConfig};
use adk_rust::futures::StreamExt;
use std::sync::Arc;
// 이전과 같이 파이프라인 생성...
// 상세 제어를 위해 Launcher 대신 Runner 사용
let session_service = Arc::new(InMemorySessionService::new());
let runner = Runner::new(RunnerConfig {
app_name: "workflow_trace".to_string(),
agent: Arc::new(pipeline),
session_service: session_service.clone(),
artifact_service: None,
memory_service: None,
run_config: None,
})?;
let session = session_service.create(CreateRequest {
app_name: "workflow_trace".to_string(),
user_id: "user".to_string(),
session_id: None,
state: Default::default(),
}).await?;
let mut stream = runner.run(
"user".to_string(),
session.id().to_string(),
Content::new("user").with_text("Analyze Rust"),
).await?;
// 각 이벤트를 처리하여 워크플로우 실행 확인
while let Some(event) = stream.next().await {
let event = event?;
// 어떤 Agent가 응답하는지 보여줍니다
println!("📍 Agent: {}", event.author);
// 응답 Content를 보여줍니다
if let Some(content) = event.content() {
for part in &content.parts {
if let Part::Text { text } = part {
println!(" {}", text);
}
}
}
println!();
}
API 레퍼런스
SequentialAgent
SequentialAgent::new("name", vec![agent1, agent2, agent3])
.with_description("Optional description")
.before_callback(callback) // 실행 전에 호출됩니다
.after_callback(callback) // 실행 후에 호출됩니다
ParallelAgent
ParallelAgent::new("name", vec![agent1, agent2, agent3])
.with_description("Optional description")
.before_callback(callback)
.after_callback(callback)
LoopAgent
LoopAgent::new("name", vec![agent1, agent2])
.with_max_iterations(5) // 안전 제한 (권장)
.with_description("Optional description")
.before_callback(callback)
.after_callback(callback)
ConditionalAgent
ConditionalAgent::new("name", |ctx| condition_fn, if_agent)
.with_else(else_agent) // 선택적 else 분기
.with_description("Optional description")
ExitLoopTool
// LoopAgent를 종료할 수 있도록 Agent에 추가합니다
.tool(Arc::new(ExitLoopTool::new()))
이전: LlmAgent | 다음: Multi-Agent 시스템 →