워크플로 에이전트
워크플로 에이전트는 여러 에이전트를 순차 파이프라인, 병렬 실행 또는 반복 루프와 같이 예측 가능한 패턴으로 조정합니다. AI 추론을 사용하는 LlmAgent와 달리, 워크플로 에이전트는 결정론적인 실행 경로를 따릅니다.
빠른 시작
새 프로젝트를 생성합니다:
cargo new workflow_demo
cd workflow_demo
Cargo.toml에 종속성을 추가합니다:
[dependencies]
adk-rust = "2.1.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-3.7-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.
작동 방식
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Researcher │ → │ Analyzer │ → │ Summarizer │
│ (step 1) │ │ (step 2) │ │ (step 3) │
└─────────────┘ └─────────────┘ └─────────────┘
↓ ↓ ↓
"Key facts..." "Insights..." "Executive summary"
- 사용자 메시지가 첫 번째 에이전트(조사자)에게 전달됩니다.
- 조사자의 응답이 기록에 추가됩니다.
- 분석자는 사용자 메시지와 조사자의 응답을 확인합니다.
- 요약자는 사용자 메시지, 조사자의 응답 및 분석자의 응답을 확인합니다.
- 마지막 에이전트가 완료되면 파이프라인이 완료됩니다.
ParallelAgent
ParallelAgent는 모든 하위 에이전트를 동시에 실행합니다. 각 에이전트는 동일한 입력을 받고 독립적으로 작업합니다.
독립성은 단순한 의도가 아니라 강제됩니다. 각 하위 에이전트는 자체
대화 브랜치({parent}.{parallel_agent}.{sub_agent})에서 실행되며, 기록 읽기 작업도
해당 브랜치로 범위가 제한됩니다. 하위 에이전트는 분기되기 전까지의
대화는 확인할 수 있지만 형제 에이전트가 생성한 내용은 확인할 수 없으므로,
동시에 실행되는 브랜치가 서로의 컨텍스트를 오염시킬 수 없습니다. 하위
에이전트가 협력하도록 하려면 ParallelAgent::with_shared_state()를 사용합니다.
브랜치는 함께 폴링되므로 실제 경과 시간 비용은 모든 브랜치의 합계가 아니라 대략 가장 느린 브랜치의 시간에 해당하며, 느린 브랜치가 다른 브랜치의 진행을 막지 않습니다. 다음 두 가지 결과를 고려하여 설계할 필요가 있습니다:
- 이벤트는 서로 뒤섞입니다. 이벤트는 하위 에이전트별로 그룹화되지 않고 브랜치가 생성하는 순서대로 도착합니다. 청크를 해당 브랜치에 연결하려면
event.author(또는event.branch)를 사용하세요. 브랜치 간 순서는 보장되지 않습니다. - 하나의 오류가 실행을 종료하며, 결정론적으로 선택됩니다. 모든 브랜치는 계속 실행되어 완료되고, 이후 단일 종료 오류가 표시됩니다. 둘 이상의 브랜치가 실패하면 보고되는 오류는 실제 시간상 먼저 실패한 브랜치의 오류가 아니라 선언 순서에서 가장 앞선 하위 에이전트의 오류입니다. 동시 실행에서는 실제 시간상 먼저 실패한 오류를 선택할 경우 경쟁 상태가 발생하기 때문입니다.
이벤트 스트림을 너무 일찍 삭제하면 아직 실행 중인 브랜치가 종료됩니다.
사용 시점
- 동일한 주제에 대한 여러 관점
- 팬아웃 처리(동일한 입력, 서로 다른 분석)
- 속도가 중요한 멀티태스크 시나리오
전체 예제
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")?);
// 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(())
}
💡 팁: 병렬 에이전트 지침을 고유한 페르소나, 집중 영역, 응답 접두사로 서로 명확히 구분하세요. 이렇게 하면 각 에이전트가 고유한 출력을 생성할 수 있습니다.
예시 상호작용
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)
모든 에이전트가 동시에 시작하며 결과는 완료되는 즉시 스트리밍됩니다.
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-3.7-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-3.7-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::builder("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: How do I fix a borrow error in Rust?
[Routing to: technical]
[Agent: tech_expert]
A borrow error occurs when Rust's ownership rules are violated...
You: What's the capital of France?
[Routing to: general]
[Agent: general_helper]
The capital of France is Paris! It's a beautiful city...
You: Write me a haiku about the moon
[Routing to: creative]
[Agent: creative_writer]
Silver orb above,
Shadows dance on silent waves—
Night whispers secrets.
작동 방식
┌─────────────────┐
│ User Message │
└────────┬────────┘
↓
┌─────────────────┐
│ LLM Classifies│ "technical" / "general" / "creative"
│ (smart_router)│
└────────┬────────┘
↓
┌────┴────┬──────────┐
↓ ↓ ↓
┌───────┐ ┌───────┐ ┌─────────┐
│ tech │ │general│ │creative │
│expert │ │helper │ │ writer │
└───────┘ └───────┘ └─────────┘
워크플로 에이전트 결합
복잡한 패턴을 위해 워크플로 에이전트를 중첩할 수 있습니다.
순차 + 병렬 + 루프
use adk_rust::prelude::*;
use std::sync::Arc;
// 1. Parallel analysis from multiple perspectives
let parallel_analysis = ParallelAgent::new(
"multi_analysis",
vec![Arc::new(tech_analyst), Arc::new(biz_analyst)],
);
// 2. Synthesize the parallel results
let synthesizer = LlmAgentBuilder::new("synthesizer")
.instruction("Combine all analyses into a unified recommendation.")
.model(model.clone())
.build()?;
// 3. Quality loop: critique and refine
let quality_loop = LoopAgent::new(
"quality_check",
vec![Arc::new(critic), Arc::new(refiner)],
).with_max_iterations(2);
// Final pipeline: 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::{SessionId, UserId};
use adk_rust::runner::{Runner, RunnerConfig};
use adk_rust::futures::StreamExt;
use std::sync::Arc;
// Create pipeline as before...
// Use Runner instead of Launcher for detailed control
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(
UserId::new("user")?,
SessionId::new(session.id())?,
Content::new("user").with_text("Analyze Rust"),
).await?;
// Process each event to see workflow execution
while let Some(event) = stream.next().await {
let event = event?;
// Show which agent is responding
println!("📍 Agent: {}", event.author);
// Show the response 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) // Called before execution
.after_callback(callback) // Called after execution
ParallelAgent
ParallelAgent::new("name", vec![agent1, agent2, agent3])
.with_description("Optional description")
.before_callback(callback)
.after_callback(callback)
하위 에이전트 중 하나라도 실패하면 ParallelAgent는 첫 번째 오류를 전파하기 전에 남아 있는 모든 future를 소진하여 리소스 누수를 방지합니다.
LoopAgent
LoopAgent::new("name", vec![agent1, agent2])
.with_max_iterations(5) // Safety limit (recommended, default: 1000)
.with_description("Optional description")
.before_callback(callback)
.after_callback(callback)
ConditionalAgent
ConditionalAgent::new("name", |ctx| condition_fn, if_agent)
.with_else(else_agent) // Optional else branch
.with_description("Optional description")
ExitLoopTool
// Add to an agent to let it exit a LoopAgent
.tool(Arc::new(ExitLoopTool::new()))
이전: LlmAgent | 다음: 멀티 에이전트 시스템 →