ग्राफ़ एजेंट्स
मूल ADK-Rust एकीकरण के साथ LangGraph-शैली ऑर्केस्ट्रेशन का उपयोग करके जटिल, स्टेटफुल वर्कफ़्लो बनाएं।
अवलोकन
GraphAgent आपको नोड्स और एजेस वाले निर्देशित ग्राफ़ के रूप में वर्कफ़्लो परिभाषित करने देता है, जो निम्न का समर्थन करता है:
- AgentNode: कस्टम इनपुट/आउटपुट मैपर के साथ LLM एजेंट्स को ग्राफ़ नोड्स के रूप में रैप करें
- चक्रीय वर्कफ़्लो: लूप्स और iterative reasoning के लिए मूल समर्थन (ReAct पैटर्न)
- सशर्त रूटिंग: state के आधार पर गतिशील edge routing
- State प्रबंधन: reducers के साथ typed state (overwrite, append, sum, custom)
- Checkpointing: fault tolerance और human-in-the-loop के लिए persistent state
- Streaming: कई stream modes (values, updates, messages, debug)
adk-graph crate जटिल, स्टेटफुल agent workflows बनाने के लिए LangGraph-शैली वर्कफ़्लो ऑर्केस्ट्रेशन प्रदान करता है। यह ADK-Rust ecosystem में graph-based workflow क्षमताएँ लाता है, जबकि ADK की agent system के साथ पूर्ण compatibility बनाए रखता है।
मुख्य लाभ:
- दृश्य वर्कफ़्लो डिज़ाइन: जटिल logic को सहज node-and-edge graphs के रूप में परिभाषित करें
- समानांतर निष्पादन: बेहतर performance के लिए कई nodes एक साथ चल सकते हैं
- State स्थायित्व: fault tolerance और human-in-the-loop के लिए अंतर्निहित checkpointing
- LLM एकीकरण: ADK agents को graph nodes के रूप में रैप करने के लिए मूल समर्थन
- लचीली रूटिंग: static edges, conditional routing, और dynamic decision making
आप क्या बनाएंगे
इस गाइड में, आप एक Text Processing Pipeline बनाएंगे जो translation और summarization को parallel में चलाता है:
┌─────────────────────┐
User Input │ │
────────────────▶ │ START │
│ │
└──────────┬──────────┘
│
┌───────────────┴───────────────┐
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ TRANSLATOR │ │ SUMMARIZER │
│ │ │ │
│ 🇫🇷 French │ │ 📝 One sentence │
│ Translation │ │ Summary │
└─────────┬────────┘ └─────────┬────────┘
│ │
└───────────────┬───────────────┘
│
▼
┌─────────────────────┐
│ COMBINE │
│ │
│ 📋 Merge Results │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ END │
│ │
│ ✅ Complete │
└─────────────────────┘
मुख्य अवधारणाएँ:
- नोड्स - प्रसंस्करण इकाइयाँ जो काम करती हैं (LLM agents, functions, या custom logic)
- एजेस - नोड्स के बीच control flow (static connections या conditional routing)
- State - साझा डेटा जो ग्राफ़ से होकर प्रवाहित होता है और नोड्स के बीच बना रहता है
- समानांतर निष्पादन - बेहतर performance के लिए कई nodes एक साथ चल सकते हैं
मुख्य घटकों को समझना
🔧 नोड्स: कार्यकर्ता नोड्स वह जगह हैं जहाँ वास्तविक काम होता है। हर node कर सकता है:
- AgentNode: प्राकृतिक भाषा संसाधित करने के लिए एक LLM agent को रैप करें
- Function Node: डेटा प्रसंस्करण के लिए custom Rust code चलाएँ
- Built-in Nodes: counters या validators जैसी पूर्वनिर्धारित logic का उपयोग करें
नोड्स को assembly line में विशिष्ट कार्यकर्ताओं की तरह समझें - प्रत्येक की अपनी विशिष्ट भूमिका और विशेषज्ञता होती है।
🔀 एजेस: प्रवाह नियंत्रण एजेस निर्धारित करते हैं कि निष्पादन आपके ग्राफ़ में कैसे आगे बढ़ता है:
- Static Edges: प्रत्यक्ष कनेक्शन (
A → B → C) - Conditional Edges: state के आधार पर गतिशील रूटिंग (
if sentiment == "positive" → positive_handler) - Parallel Edges: एक node से कई पथ (
START → [translator, summarizer])
एजेस traffic signals और road signs की तरह होते हैं जो काम के प्रवाह को निर्देशित करते हैं।
💾 State: साझा स्मृति State एक key-value store है जिसे सभी nodes पढ़ और लिख सकते हैं:
- Input Data: ग्राफ़ में डाली गई प्रारंभिक जानकारी
- Intermediate Results: एक node का output दूसरे के लिए input बन जाता है
- Final Output: सभी प्रसंस्करण के बाद पूरा हुआ परिणाम
State एक साझा whiteboard की तरह काम करता है जहाँ nodes दूसरों के उपयोग के लिए जानकारी छोड़ सकते हैं।
⚡ समानांतर निष्पादन: गति बढ़ाने वाला जब किसी node से कई edges निकलते हैं, तो वे target nodes एक साथ चलते हैं:
- तेज़ प्रसंस्करण: स्वतंत्र कार्य एक ही समय पर चलते हैं
- संसाधन दक्षता: CPU और I/O का बेहतर उपयोग
- स्केलेबिलिटी: रैखिक धीमापन के बिना अधिक जटिल workflows संभालें
यह ऐसा है जैसे किसी काम के अलग-अलग हिस्सों को एक साथ कई worker संभाल रहे हों, बजाय इसके कि वे कतार में प्रतीक्षा करें।
त्वरित आरंभ
1. अपना प्रोजेक्ट बनाएं
cargo new graph_demo
cd graph_demo
Cargo.toml में dependencies जोड़ें:
[dependencies]
adk-graph = { version = "2.0.0", features = ["sqlite"] }
adk-agent = "2.0.0"
adk-model = "2.0.0"
adk-core = "2.0.0"
tokio = { version = "1", features = ["full"] }
dotenvy = "0.15"
serde_json = "1.0"
अपनी API key के साथ .env बनाएं:
echo 'GOOGLE_API_KEY=your-api-key' > .env
2. समानांतर प्रसंस्करण उदाहरण
यहाँ एक पूर्ण, कार्यरत उदाहरण है जो text को parallel में संसाधित करता है:
use adk_agent::LlmAgentBuilder;
use adk_graph::{
agent::GraphAgent,
edge::{END, START},
node::{AgentNode, ExecutionConfig, NodeOutput},
state::State,
};
use adk_model::GeminiModel;
use serde_json::json;
use std::sync::Arc;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
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 specialized LLM agents
let translator_agent = Arc::new(
LlmAgentBuilder::new("translator")
.description("Translates text to French")
.model(model.clone())
.instruction("Translate the input text to French. Only output the translation.")
.build()?,
);
let summarizer_agent = Arc::new(
LlmAgentBuilder::new("summarizer")
.description("Summarizes text")
.model(model.clone())
.instruction("Summarize the input text in one sentence.")
.build()?,
);
// Wrap agents as graph nodes with input/output mappers
let translator_node = AgentNode::new(translator_agent)
.with_input_mapper(|state| {
let text = state.get("input").and_then(|v| v.as_str()).unwrap_or("");
adk_core::Content::new("user").with_text(text)
})
.with_output_mapper(|events| {
let mut updates = std::collections::HashMap::new();
for event in events {
if let Some(content) = event.content() {
let text: String = content.parts.iter()
.filter_map(|p| p.text())
.collect::<Vec<_>>()
.join("");
if !text.is_empty() {
updates.insert("translation".to_string(), json!(text));
}
}
}
updates
});
let summarizer_node = AgentNode::new(summarizer_agent)
.with_input_mapper(|state| {
let text = state.get("input").and_then(|v| v.as_str()).unwrap_or("");
adk_core::Content::new("user").with_text(text)
})
.with_output_mapper(|events| {
let mut updates = std::collections::HashMap::new();
for event in events {
if let Some(content) = event.content() {
let text: String = content.parts.iter()
.filter_map(|p| p.text())
.collect::<Vec<_>>()
.join("");
if !text.is_empty() {
updates.insert("summary".to_string(), json!(text));
}
}
}
updates
});
// Build the graph with parallel execution
let agent = GraphAgent::builder("text_processor")
.description("Processes text with translation and summarization in parallel")
.channels(&["input", "translation", "summary", "result"])
.node(translator_node)
.node(summarizer_node)
.node_fn("combine", |ctx| async move {
let translation = ctx.get("translation").and_then(|v| v.as_str()).unwrap_or("N/A");
let summary = ctx.get("summary").and_then(|v| v.as_str()).unwrap_or("N/A");
let result = format!(
"=== Processing Complete ===\n\n\
French Translation:\n{}\n\n\
Summary:\n{}",
translation, summary
);
Ok(NodeOutput::new().with_update("result", json!(result)))
})
// Parallel execution: both nodes start simultaneously
.edge(START, "translator")
.edge(START, "summarizer")
.edge("translator", "combine")
.edge("summarizer", "combine")
.edge("combine", END)
.build()?;
// Execute the graph
let mut input = State::new();
input.insert("input".to_string(), json!("AI is transforming how we work and live."));
let result = agent.invoke(input, ExecutionConfig::new("thread-1")).await?;
println!("{}", result.get("result").and_then(|v| v.as_str()).unwrap_or(""));
Ok(())
}
उदाहरण आउटपुट:
=== Processing Complete ===
French Translation:
L'IA transforme notre façon de travailler et de vivre.
Summary:
AI is revolutionizing work and daily life through technological transformation.
ग्राफ़ निष्पादन कैसे काम करता है
समग्र दृष्टि
Graph agents super-steps में निष्पादित होते हैं - सभी तैयार nodes parallel में चलते हैं, फिर ग्राफ़ अगले चरण से पहले सभी के पूरा होने की प्रतीक्षा करता है:
Step 1: START ──┬──▶ translator (running)
└──▶ summarizer (running)
⏳ Wait for both to complete...
Step 2: translator ──┬──▶ combine (running)
summarizer ──┘
⏳ Wait for combine to complete...
Step 3: combine ──▶ END ✅
Nodes के माध्यम से state प्रवाह
हर node साझा state से पढ़ और उसमें लिख सकता है:
┌─────────────────────────────────────────────────────────────────────┐
│ STEP 1: Initial state │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ State: { "input": "AI is transforming how we work" } │
│ │
│ ↓ │
│ │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ translator │ │ summarizer │ │
│ │ reads "input" │ │ reads "input" │ │
│ └──────────────────┘ └──────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────┐
│ STEP 2: After parallel execution │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ State: { │
│ "input": "AI is transforming how we work", │
│ "translation": "L'IA transforme notre façon de travailler", │
│ "summary": "AI is revolutionizing work through technology" │
│ } │
│ │
│ ↓ │
│ │
│ ┌──────────────────────────────────────┐ │
│ │ combine │ │
│ │ reads "translation" + "summary" │ │
│ │ writes "result" │ │
│ └──────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────────┐
│ STEP 3: Final state │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ State: { │
│ "input": "AI is transforming how we work", │
│ "translation": "L'IA transforme notre façon de travailler", │
│ "summary": "AI is revolutionizing work through technology", │
│ "result": "=== Processing Complete ===\n\nFrench..." │
│ } │
│ │
└─────────────────────────────────────────────────────────────────────┘
यह कैसे काम करता है
| घटक | भूमिका |
|---|---|
AgentNode | इनपुट/आउटपुट मैपरों के साथ LLM एजेंटों को रैप करता है |
input_mapper | स्टेट → एजेंट इनपुट Content में रूपांतरित करता है |
output_mapper | एजेंट इवेंट्स को → स्टेट अपडेट्स में बदलता है |
channels | ग्राफ द्वारा उपयोग किए जाने वाले स्टेट फ़ील्ड्स घोषित करता है |
edge() | नोड्स के बीच निष्पादन प्रवाह को परिभाषित करता है |
ExecutionConfig | चेकपॉइंटिंग के लिए थ्रेड ID प्रदान करता है |
LLM वर्गीकरण के साथ सशर्त रूटिंग
ऐसी स्मार्ट रूटिंग प्रणालियाँ बनाएँ जहाँ LLMs निष्पादन पथ तय करते हैं:
दृश्य: भावना-आधारित रूटिंग
┌─────────────────────┐
User Feedback │ │
────────────────▶ │ CLASSIFIER │
│ 🧠 Analyze tone │
└──────────┬──────────┘
│
┌───────────────┼───────────────┐
│ │ │
▼ ▼ ▼
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ POSITIVE │ │ NEGATIVE │ │ NEUTRAL │
│ │ │ │ │ │
│ 😊 Thank you! │ │ 😔 Apologize │ │ 😐 Ask more │
│ Celebrate │ │ Help fix │ │ questions │
└──────────────────┘ └──────────────────┘ └──────────────────┘
पूर्ण उदाहरण कोड
use adk_agent::LlmAgentBuilder;
use adk_graph::{
edge::{END, Router, START},
graph::StateGraph,
node::{AgentNode, ExecutionConfig},
state::State,
};
use adk_model::GeminiModel;
use serde_json::json;
use std::sync::Arc;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
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 classifier agent
let classifier_agent = Arc::new(
LlmAgentBuilder::new("classifier")
.description("Classifies text sentiment")
.model(model.clone())
.instruction(
"You are a sentiment classifier. Analyze the input text and respond with \
ONLY one word: 'positive', 'negative', or 'neutral'. Nothing else.",
)
.build()?,
);
// Create response agents for each sentiment
let positive_agent = Arc::new(
LlmAgentBuilder::new("positive")
.description("Handles positive feedback")
.model(model.clone())
.instruction(
"You are a customer success specialist. The customer has positive feedback. \
Express gratitude, reinforce the positive experience, and suggest ways to \
share their experience. Be warm and appreciative. Keep response under 3 sentences.",
)
.build()?,
);
let negative_agent = Arc::new(
LlmAgentBuilder::new("negative")
.description("Handles negative feedback")
.model(model.clone())
.instruction(
"You are a customer support specialist. The customer has a complaint. \
Acknowledge their frustration, apologize sincerely, and offer help. \
Be empathetic. Keep response under 3 sentences.",
)
.build()?,
);
let neutral_agent = Arc::new(
LlmAgentBuilder::new("neutral")
.description("Handles neutral feedback")
.model(model.clone())
.instruction(
"You are a customer service representative. The customer has neutral feedback. \
Ask clarifying questions to better understand their needs. Be helpful and curious. \
Keep response under 3 sentences.",
)
.build()?,
);
// Create AgentNodes with mappers
let classifier_node = AgentNode::new(classifier_agent)
.with_input_mapper(|state| {
let text = state.get("feedback").and_then(|v| v.as_str()).unwrap_or("");
adk_core::Content::new("user").with_text(text)
})
.with_output_mapper(|events| {
let mut updates = std::collections::HashMap::new();
for event in events {
if let Some(content) = event.content() {
let text: String = content.parts.iter()
.filter_map(|p| p.text())
.collect::<Vec<_>>()
.join("")
.to_lowercase()
.trim()
.to_string();
let sentiment = if text.contains("positive") { "positive" }
else if text.contains("negative") { "negative" }
else { "neutral" };
updates.insert("sentiment".to_string(), json!(sentiment));
}
}
updates
});
// Response nodes (similar pattern for each)
let positive_node = AgentNode::new(positive_agent)
.with_input_mapper(|state| {
let text = state.get("feedback").and_then(|v| v.as_str()).unwrap_or("");
adk_core::Content::new("user").with_text(text)
})
.with_output_mapper(|events| {
let mut updates = std::collections::HashMap::new();
for event in events {
if let Some(content) = event.content() {
let text: String = content.parts.iter()
.filter_map(|p| p.text())
.collect::<Vec<_>>()
.join("");
updates.insert("response".to_string(), json!(text));
}
}
updates
});
// Build graph with conditional routing
let graph = StateGraph::with_channels(&["feedback", "sentiment", "response"])
.add_node(classifier_node)
.add_node(positive_node)
// ... add negative_node and neutral_node similarly
.add_edge(START, "classifier")
.add_conditional_edges(
"classifier",
Router::by_field("sentiment"), // Route based on sentiment field
[
("positive", "positive"),
("negative", "negative"),
("neutral", "neutral"),
],
)
.add_edge("positive", END)
.add_edge("negative", END)
.add_edge("neutral", END)
.compile()?;
// Test with different feedback
let mut input = State::new();
input.insert("feedback".to_string(), json!("Your product is amazing! I love it!"));
let result = graph.invoke(input, ExecutionConfig::new("feedback-1")).await?;
println!("Sentiment: {}", result.get("sentiment").and_then(|v| v.as_str()).unwrap_or(""));
println!("Response: {}", result.get("response").and_then(|v| v.as_str()).unwrap_or(""));
Ok(())
}
उदाहरण प्रवाह:
Input: "Your product is amazing! I love it!"
↓
Classifier: "positive"
↓
Positive Agent: "Thank you so much for the wonderful feedback!
We're thrilled you love our product.
Would you consider leaving a review to help others?"
ReAct पैटर्न: तर्क + क्रिया
ऐसे एजेंट बनाएँ जो जटिल समस्याओं को हल करने के लिए टूल्स का क्रमिक रूप से उपयोग कर सकें:
दृश्य: ReAct चक्र
┌─────────────────────┐
User Question │ │
────────────────▶ │ REASONER │
│ 🧠 Think + Act │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ Has tool calls? │
│ │
└──────────┬──────────┘
│
┌───────────────┴───────────────┐
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ YES │ │ NO │
│ │ │ │
│ 🔄 Loop back │ │ ✅ Final answer │
│ to reasoner │ │ END │
└─────────┬────────┘ └──────────────────┘
│
└─────────────────┐
│
▼
┌─────────────────────┐
│ REASONER │
│ 🧠 Think + Act │
│ (next iteration) │
└─────────────────────┘
पूर्ण ReAct उदाहरण
use adk_agent::LlmAgentBuilder;
use adk_core::{Part, Tool};
use adk_graph::{
edge::{END, START},
graph::StateGraph,
node::{AgentNode, ExecutionConfig, NodeOutput},
state::State,
};
use adk_model::GeminiModel;
use adk_tool::FunctionTool;
use serde_json::json;
use std::sync::Arc;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
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 tools
let weather_tool = Arc::new(FunctionTool::new(
"get_weather",
"Get the current weather for a location. Takes a 'location' parameter (city name).",
|_ctx, args| async move {
let location = args.get("location").and_then(|v| v.as_str()).unwrap_or("unknown");
Ok(json!({
"location": location,
"temperature": "72°F",
"condition": "Sunny",
"humidity": "45%"
}))
},
)) as Arc<dyn Tool>;
let calculator_tool = Arc::new(FunctionTool::new(
"calculator",
"Perform mathematical calculations. Takes an 'expression' parameter (string).",
|_ctx, args| async move {
let expr = args.get("expression").and_then(|v| v.as_str()).unwrap_or("0");
let result = match expr {
"2 + 2" => "4",
"10 * 5" => "50",
"100 / 4" => "25",
"15 - 7" => "8",
_ => "Unable to evaluate",
};
Ok(json!({ "result": result, "expression": expr }))
},
)) as Arc<dyn Tool>;
// Create reasoner agent with tools
let reasoner_agent = Arc::new(
LlmAgentBuilder::new("reasoner")
.description("Reasoning agent with tools")
.model(model.clone())
.instruction(
"You are a helpful assistant with access to tools. Use tools when needed to answer questions. \
When you have enough information, provide a final answer without using more tools.",
)
.tool(weather_tool)
.tool(calculator_tool)
.build()?,
);
// Create reasoner node that detects tool usage
let reasoner_node = AgentNode::new(reasoner_agent)
.with_input_mapper(|state| {
let question = state.get("question").and_then(|v| v.as_str()).unwrap_or("");
adk_core::Content::new("user").with_text(question)
})
.with_output_mapper(|events| {
let mut updates = std::collections::HashMap::new();
let mut has_tool_calls = false;
let mut response = String::new();
for event in events {
if let Some(content) = event.content() {
for part in &content.parts {
match part {
Part::FunctionCall { .. } => {
has_tool_calls = true;
}
Part::Text { text } => {
response.push_str(text);
}
_ => {}
}
}
}
}
updates.insert("has_tool_calls".to_string(), json!(has_tool_calls));
updates.insert("response".to_string(), json!(response));
updates
});
// Build ReAct graph with cycle
let graph = StateGraph::with_channels(&["question", "has_tool_calls", "response", "iteration"])
.add_node(reasoner_node)
.add_node_fn("counter", |ctx| async move {
let i = ctx.get("iteration").and_then(|v| v.as_i64()).unwrap_or(0);
Ok(NodeOutput::new().with_update("iteration", json!(i + 1)))
})
.add_edge(START, "counter")
.add_edge("counter", "reasoner")
.add_conditional_edges(
"reasoner",
|state| {
let has_tools = state.get("has_tool_calls").and_then(|v| v.as_bool()).unwrap_or(false);
let iteration = state.get("iteration").and_then(|v| v.as_i64()).unwrap_or(0);
// Safety limit
if iteration >= 5 { return END.to_string(); }
if has_tools {
"counter".to_string() // Loop back for more reasoning
} else {
END.to_string() // Done - final answer
}
},
[("counter", "counter"), (END, END)],
)
.compile()?
.with_recursion_limit(10);
// Test the ReAct agent
let mut input = State::new();
input.insert("question".to_string(), json!("What's the weather in Paris and what's 15 + 25?"));
let result = graph.invoke(input, ExecutionConfig::new("react-1")).await?;
println!("Final answer: {}", result.get("response").and_then(|v| v.as_str()).unwrap_or(""));
println!("Iterations: {}", result.get("iteration").and_then(|v| v.as_i64()).unwrap_or(0));
Ok(())
}
उदाहरण प्रवाह:
Question: "What's the weather in Paris and what's 15 + 25?"
Iteration 1:
Reasoner: "I need to get weather info and do math"
→ Calls get_weather(location="Paris") and calculator(expression="15 + 25")
→ has_tool_calls = true → Loop back
Iteration 2:
Reasoner: "Based on the results: Paris is 72°F and sunny, 15 + 25 = 40"
→ No tool calls → has_tool_calls = false → END
Final Answer: "The weather in Paris is 72°F and sunny with 45% humidity.
And 15 + 25 equals 40."
AgentNode
किसी भी ADK Agent (आमतौर पर LlmAgent) को एक ग्राफ़ नोड के रूप में लपेटता है:
एजेंट क्या देखता है
ग्राफ़ के अंदर एक एजेंट उस invocation से व्युत्पन्न संदर्भ के तहत चलता है जिसने ग्राफ़ शुरू किया था, इसलिए वह वैसा ही व्यवहार करता है जैसा वह बाहर करता है। जब एक Runner एक GraphAgent को invoke करता है, तो कॉलर की पहचान और सेवाएँ स्वचालित रूप से आगे बढ़ाई जाती हैं:
| साथ ले जाया गया | नोट |
|---|---|
app_name, user_id, session_id | कॉलर का, न कि एक कृत्रिम वाला |
| Scopes और अनुरोध मेटाडेटा | ताकि scope जाँचें कॉलर के grants देख सकें |
| गुप्त सेवा, मेमोरी, आर्टिफैक्ट्स, साझा स्थिति | ग्राफ़ के बाहर जैसी ही उपलब्ध |
| रद्दीकरण | Runner::interrupt किसी एजेंट तक पहुँचता है जो एक नोड के रूप में चल रहा है |
RunConfig | कॉलर से विरासत में मिला |
branch | व्युत्पन्न, {caller_branch}.{agent_name} के रूप में, ताकि एक नोड की घटनाएँ जिम्मेदार ठहराई जा सकें |
एक graph जिसे सीधे invoke किया गया है — graph.invoke(state, ExecutionConfig::new("thread")) —
के पास inherit करने के लिए कोई invocation नहीं होता। यह standalone mode है: node को
user_id = "graph_user", app_name = "graph_app", branch main, कोई secrets नहीं, और कोई
memory नहीं मिलता। यह graph को Runner के बाहर चलाने के लिए एक जानबूझकर चुना गया mode है, न कि production में अपनाने के लिए कोई fallback।
इसे manually bridge करने के लिए — उदाहरण के लिए जब आप अपने own executor से graph चला रहे हों — invocation को explicitly pass करें:
let config = ExecutionConfig::new(ctx.session_id()).with_parent_context(ctx.clone());
नोट: node फिर भी अपने अलग in-memory graph session पर चलता है, इसलिए node के अंदर agent conversation history node तक सीमित रहती है, caller's session में append नहीं की जाती।
let node = AgentNode::new(llm_agent)
.with_input_mapper(|state| {
// Transform graph state to agent input Content
let text = state.get("input").and_then(|v| v.as_str()).unwrap_or("");
adk_core::Content::new("user").with_text(text)
})
.with_output_mapper(|events| {
// Transform agent events to state updates
let mut updates = std::collections::HashMap::new();
for event in events {
if let Some(content) = event.content() {
let text: String = content.parts.iter()
.filter_map(|p| p.text())
.collect::<Vec<_>>()
.join("");
updates.insert("output".to_string(), json!(text));
}
}
updates
});
Function Nodes
State को process करने वाले सरल async functions:
.node_fn("process", |ctx| async move {
let input = ctx.state.get("input").unwrap();
let output = process_data(input).await?;
Ok(NodeOutput::new().with_update("output", output))
})
Edge Types
Static Edges
Nodes के बीच direct connections:
.edge(START, "first_node")
.edge("first_node", "second_node")
.edge("second_node", END)
Conditional Edges
State के आधार पर dynamic routing:
.conditional_edge(
"router",
|state| {
match state.get("next").and_then(|v| v.as_str()) {
Some("research") => "research_node".to_string(),
Some("write") => "write_node".to_string(),
_ => END.to_string(),
}
},
[
("research_node", "research_node"),
("write_node", "write_node"),
(END, END),
],
)
Router Helpers
Common patterns के लिए built-in routers का उपयोग करें:
use adk_graph::edge::Router;
// Route based on a state field value
.conditional_edge("classifier", Router::by_field("sentiment"), [
("positive", "positive_handler"),
("negative", "negative_handler"),
("neutral", "neutral_handler"),
])
// Route based on boolean field
.conditional_edge("check", Router::by_bool("approved"), [
("true", "execute"),
("false", "reject"),
])
// Limit iterations
.conditional_edge("loop", Router::max_iterations("count", 5), [
("continue", "process"),
("done", END),
])
Parallel Execution
एक single node से multiple edges parallel में execute होते हैं:
let agent = GraphAgent::builder("parallel_processor")
.channels(&["input", "translation", "summary", "analysis"])
.node(translator_node)
.node(summarizer_node)
.node(analyzer_node)
.node(combiner_node)
// All three start simultaneously
.edge(START, "translator")
.edge(START, "summarizer")
.edge(START, "analyzer")
// Wait for all to complete before combining
.edge("translator", "combiner")
.edge("summarizer", "combiner")
.edge("analyzer", "combiner")
.edge("combiner", END)
.build()?;
Cyclic Graphs (ReAct Pattern)
Cycles के साथ iterative reasoning agents बनाएं:
use adk_core::Part;
// Create agent with tools
let reasoner = Arc::new(
LlmAgentBuilder::new("reasoner")
.model(model)
.instruction("Use tools to answer questions. Provide final answer when done.")
.tool(search_tool)
.tool(calculator_tool)
.build()?
);
let reasoner_node = AgentNode::new(reasoner)
.with_input_mapper(|state| {
let question = state.get("question").and_then(|v| v.as_str()).unwrap_or("");
adk_core::Content::new("user").with_text(question)
})
.with_output_mapper(|events| {
let mut updates = std::collections::HashMap::new();
let mut has_tool_calls = false;
let mut response = String::new();
for event in events {
if let Some(content) = event.content() {
for part in &content.parts {
match part {
Part::FunctionCall { name, .. } => {
has_tool_calls = true;
}
Part::Text { text } => {
response.push_str(text);
}
_ => {}
}
}
}
}
updates.insert("has_tool_calls".to_string(), json!(has_tool_calls));
updates.insert("response".to_string(), json!(response));
updates
});
// Build graph with cycle
let react_agent = StateGraph::with_channels(&["question", "has_tool_calls", "response", "iteration"])
.add_node(reasoner_node)
.add_node_fn("counter", |ctx| async move {
let i = ctx.get("iteration").and_then(|v| v.as_i64()).unwrap_or(0);
Ok(NodeOutput::new().with_update("iteration", json!(i + 1)))
})
.add_edge(START, "counter")
.add_edge("counter", "reasoner")
.add_conditional_edges(
"reasoner",
|state| {
let has_tools = state.get("has_tool_calls").and_then(|v| v.as_bool()).unwrap_or(false);
let iteration = state.get("iteration").and_then(|v| v.as_i64()).unwrap_or(0);
// Safety limit
if iteration >= 5 { return END.to_string(); }
if has_tools {
"counter".to_string() // Loop back
} else {
END.to_string() // Done
}
},
[("counter", "counter"), (END, END)],
)
.compile()?
.with_recursion_limit(10);
Multi-Agent Supervisor
Tasks को specialist agents तक route करें:
// Create supervisor agent
let supervisor = Arc::new(
LlmAgentBuilder::new("supervisor")
.model(model.clone())
.instruction("Route tasks to: researcher, writer, or coder. Reply with agent name only.")
.build()?
);
let supervisor_node = AgentNode::new(supervisor)
.with_output_mapper(|events| {
let mut updates = std::collections::HashMap::new();
for event in events {
if let Some(content) = event.content() {
let text: String = content.parts.iter()
.filter_map(|p| p.text())
.collect::<Vec<_>>()
.join("")
.to_lowercase();
let next = if text.contains("researcher") { "researcher" }
else if text.contains("writer") { "writer" }
else if text.contains("coder") { "coder" }
else { "done" };
updates.insert("next_agent".to_string(), json!(next));
}
}
updates
});
// Build supervisor graph
let graph = StateGraph::with_channels(&["task", "next_agent", "research", "content", "code"])
.add_node(supervisor_node)
.add_node(researcher_node)
.add_node(writer_node)
.add_node(coder_node)
.add_edge(START, "supervisor")
.add_conditional_edges(
"supervisor",
Router::by_field("next_agent"),
[
("researcher", "researcher"),
("writer", "writer"),
("coder", "coder"),
("done", END),
],
)
// Agents report back to supervisor
.add_edge("researcher", "supervisor")
.add_edge("writer", "supervisor")
.add_edge("coder", "supervisor")
.compile()?;
State Management
State Schema with Reducers
State updates कैसे merge हों, इसे नियंत्रित करें:
let schema = StateSchema::builder()
.channel("current_step") // Overwrite (default)
.list_channel("messages") // Append to list
.channel_with_reducer("count", Reducer::Sum) // Sum values
.channel_with_reducer("data", Reducer::Custom(Arc::new(|old, new| {
// Custom merge logic
merge_json(old, new)
})))
.build();
let agent = GraphAgent::builder("stateful")
.state_schema(schema)
// ... nodes and edges
.build()?;
Reducer Types
| Reducer | व्यवहार |
|---|---|
Overwrite | पुराना मान नए मान से बदलें (डिफ़ॉल्ट) |
Append | सूची में जोड़ें |
Sum | संख्यात्मक मान जोड़ें |
Custom | कस्टम मर्ज फ़ंक्शन |
चेकपॉइंटिंग
दोष-सहिष्णुता और human-in-the-loop के लिए persistent state सक्षम करें:
In-Memory (Development)
use adk_graph::checkpoint::MemoryCheckpointer;
let checkpointer = Arc::new(MemoryCheckpointer::new());
let graph = StateGraph::with_channels(&["task", "result"])
// ... nodes and edges
.compile()?
.with_checkpointer_arc(checkpointer.clone());
SQLite (Production)
use adk_graph::checkpoint::SqliteCheckpointer;
let checkpointer = SqliteCheckpointer::new("checkpoints.db").await?;
let graph = StateGraph::with_channels(&["task", "result"])
// ... nodes and edges
.compile()?
.with_checkpointer(checkpointer);
एक Checkpoint क्या रिकॉर्ड करता है
एक checkpoint संचित state, step number, और frontier को संग्रहीत करता है — वे nodes जिन्हें अभी भी चलना है। इसे frontier के आगे बढ़ने के बाद लिखा जाता है, इसलिए resume करने पर पहले से पूर्ण हो चुके node को कभी दोबारा execute नहीं किया जाता और उसके updates कभी दो बार लागू नहीं होते। जो run समाप्त हो जाता है, वह एक खाली frontier का checkpoint बनाता है, इसलिए completed thread को resume करने पर graph को फिर से शुरू करने के बजाय final state लौटती है।
दो मामलों में जानबूझकर उस frontier का checkpoint लिया जाता है जो execute हो रहा था, बजाय अगले वाले के, क्योंकि interrupted node ने अभी तक अपने updates उत्पन्न नहीं किए होते और resume पर उसे फिर से चलना पड़ता है:
| स्थिति | फ्रंटियर सहेजा गया |
|---|---|
| सुपर-स्टेप पूरा हुआ | चलाने के लिए अगले नोड |
| रन समाप्त | रिक्त |
| इंटरप्ट उठाया गया (blocking या streaming) | वे नोड्स जो निष्पादित हो रहे थे |
स्ट्रीम किए गए रन उसी शेड्यूल पर चेकपॉइंट करते हैं जैसे ब्लॉकिंग रन, जिसमें वह स्थिति भी शामिल है जब कोई इंटरप्ट स्ट्रीम को समाप्त करता है, इसलिए human-in-the-loop विराम किसी भी execution mode में resumable है।
Checkpoint History (Time Travel)
केवल पढ़ने के लिए।
TimeTravelHandle::state_history(from, to)वह state लौटाता है जो प्रत्येक checkpointed step पर store की गई थी। यह कुछ भी execute नहीं करता — कोई node run नहीं होता, कोई event regenerate नहीं होता, और कोई side effect दोहराया नहीं जाता। history में किसी point से फिर से चलाने के लिए, उस checkpoint को branch करने के लिएfork_atका उपयोग करें और forked thread पर graph invoke करें। यह method पहलेreplayकहलाता था और इसे graph को re-execute करने के रूप में documented किया गया था, जबकि यह कभी ऐसा करता ही नहीं था।
Checkpoints durable resume भी सक्षम करते हैं — यदि graph execution crash हो जाए या process restart हो जाए, तो execution शुरुआत से फिर शुरू करने के बजाय अंतिम persisted checkpoint से resume होती है। crash-safe persistence के लिए SqliteCheckpointer या PostgresCheckpointer का उपयोग करें।
// List all checkpoints for a thread
let checkpoints = checkpointer.list("thread-id").await?;
for cp in checkpoints {
println!("Step {}: {:?}", cp.step, cp.state.get("status"));
}
// Load a specific checkpoint
if let Some(checkpoint) = checkpointer.load_by_id(&checkpoint_id).await? {
println!("State at step {}: {:?}", checkpoint.step, checkpoint.state);
}
Human-in-the-Loop
dynamic interrupts का उपयोग करके human approval के लिए execution pause करें:
use adk_graph::{error::GraphError, node::NodeOutput};
// Planner agent assesses risk
let planner_node = AgentNode::new(planner_agent)
.with_output_mapper(|events| {
let mut updates = std::collections::HashMap::new();
for event in events {
if let Some(content) = event.content() {
let text: String = content.parts.iter()
.filter_map(|p| p.text())
.collect::<Vec<_>>()
.join("");
// Extract risk level from LLM response
let risk = if text.to_lowercase().contains("risk: high") { "high" }
else if text.to_lowercase().contains("risk: medium") { "medium" }
else { "low" };
updates.insert("plan".to_string(), json!(text));
updates.insert("risk_level".to_string(), json!(risk));
}
}
updates
});
// Review node with dynamic interrupt
let graph = StateGraph::with_channels(&["task", "plan", "risk_level", "approved", "result"])
.add_node(planner_node)
.add_node(executor_node)
.add_node_fn("review", |ctx| async move {
let risk = ctx.get("risk_level").and_then(|v| v.as_str()).unwrap_or("low");
let approved = ctx.get("approved").and_then(|v| v.as_bool());
// Already approved - continue
if approved == Some(true) {
return Ok(NodeOutput::new());
}
// High/medium risk - interrupt for approval
if risk == "high" || risk == "medium" {
return Ok(NodeOutput::interrupt_with_data(
&format!("{} RISK: Human approval required", risk.to_uppercase()),
json!({
"plan": ctx.get("plan"),
"risk_level": risk,
"action": "Set 'approved' to true to continue"
})
));
}
// Low risk - auto-approve
Ok(NodeOutput::new().with_update("approved", json!(true)))
})
.add_edge(START, "planner")
.add_edge("planner", "review")
.add_edge("review", "executor")
.add_edge("executor", END)
.compile()?
.with_checkpointer_arc(checkpointer.clone());
// Execute - may pause for approval
let thread_id = "task-001";
let result = graph.invoke(input, ExecutionConfig::new(thread_id)).await;
match result {
Err(GraphError::Interrupted(interrupt)) => {
println!("*** EXECUTION PAUSED ***");
println!("Reason: {}", interrupt.interrupt);
println!("Plan awaiting approval: {:?}", interrupt.state.get("plan"));
// Human reviews and approves...
// Update state with approval
graph.update_state(thread_id, [("approved".to_string(), json!(true))]).await?;
// Resume execution
let final_result = graph.invoke(State::new(), ExecutionConfig::new(thread_id)).await?;
println!("Final result: {:?}", final_result.get("result"));
}
Ok(result) => {
println!("Completed without interrupt: {:?}", result);
}
Err(e) => {
println!("Error: {}", e);
}
}
Static Interrupts
अनिवार्य pause points के लिए interrupt_before या interrupt_after का उपयोग करें:
let graph = StateGraph::with_channels(&["task", "plan", "result"])
.add_node(planner_node)
.add_node(executor_node)
.add_edge(START, "planner")
.add_edge("planner", "executor")
.add_edge("executor", END)
.compile()?
.with_interrupt_before(&["executor"]); // Always pause before execution
Streaming Execution
graph के execute होते समय events stream करें:
use futures::StreamExt;
use adk_graph::stream::StreamMode;
let stream = agent.stream(input, config, StreamMode::Updates);
while let Some(event) = stream.next().await {
match event? {
StreamEvent::NodeStart(name) => println!("Starting: {}", name),
StreamEvent::Updates { node, updates } => {
println!("{} updated state: {:?}", node, updates);
}
StreamEvent::NodeEnd(name) => println!("Completed: {}", name),
StreamEvent::Done(state) => println!("Final state: {:?}", state),
_ => {}
}
}
Stream Modes
| मोड | विवरण |
|---|---|
Values | प्रत्येक नोड के बाद पूर्ण स्थिति स्ट्रीम करें |
Updates | केवल स्थिति परिवर्तन स्ट्रीम करें |
Messages | स्ट्रीम संदेश-प्रकार के अपडेट |
Debug | सभी आंतरिक घटनाओं को स्ट्रीम करें |
Messages मोड Node::execute_stream से टोकन उनके उत्पन्न होने पर पढ़ता है।
इस मोड में प्रत्येक नोड प्रति सुपर-स्टेप एक बार चलता है: नोड अपने state
updates को stream पर एक StreamEvent::Updates event के रूप में रिपोर्ट करता है, और executor
उन्हें नोड को state collect करने के लिए दूसरी बार चलाने के बजाय लागू करता है।
यह सबसे अधिक AgentNode के लिए महत्वपूर्ण है, जहाँ दूसरा execution का मतलब होगा
प्रति नोड दूसरा billed model call।
महत्वपूर्ण: एक custom
Nodeजोexecute_streamको override करता है, उसे अपने state updates carrying एकStreamEvent::Updatesevent emit करना चाहिए। इसके बिना नोड events stream करता है लेकिनMessagesmode में कोई state contribute नहीं करता। defaultexecute_stream, जोexecuteको wrap करता है, यह आपके लिए करता है।
Timeout policies streamed execution स्वयं पर लागू होते हैं। एक stream के लिए,
idle_timeout का मतलब है कि सीमा के भीतर कोई event produce नहीं हुआ।
ADK एकीकरण
GraphAgent ADK Agent trait को implement करता है, इसलिए यह इनके साथ काम करता है:
- Runner: standard execution के लिए
adk-runnerके साथ उपयोग करें - Callbacks: before/after callbacks के लिए पूर्ण support
- Sessions: conversation history के लिए
adk-sessionके साथ काम करता है - Streaming: ADK
EventStreamलौटाता है
use adk_runner::Runner;
let graph_agent = GraphAgent::builder("workflow")
.before_agent_callback(|ctx| async {
println!("Starting graph execution for session: {}", ctx.session_id());
Ok(())
})
.after_agent_callback(|ctx, event| async {
if let Some(content) = event.content() {
println!("Graph completed with content");
}
Ok(())
})
// ... graph definition
.build()?;
// GraphAgent implements Agent trait - use with Launcher or Runner
// See adk-runner README for Runner configuration
उदाहरण
इस repository में validated graph examples:
cargo run --manifest-path examples/tier_examples/standard/Cargo.toml --bin 11-standard-graph
cargo run --manifest-path examples/tier_examples/standard/Cargo.toml --bin 12-standard-sequential
cargo run --manifest-path examples/competitive_graph_resume/Cargo.toml
वास्तविक LLM एकीकरण वाली पूरी ग्राफ़ गैलरी इस साइट में शामिल ADK-Rust Playground में उपलब्ध है।
LangGraph के साथ तुलना
| विशेषता | LangGraph | adk-graph |
|---|---|---|
| स्थिति प्रबंधन | TypedDict + रिड्यूसर्स | StateSchema + रिड्यूसर्स |
| निष्पादन मॉडल | Pregel सुपर-स्टेप्स | Pregel सुपर-स्टेप्स |
| चेकपॉइंटिंग | Memory, SQLite, Postgres | Memory, SQLite |
| मानव-इन-द-लूप | interrupt_before/after | interrupt_before/after + dynamic |
| स्ट्रीमिंग | 5 modes | 5 modes |
| चक्र | Native support | Native support |
| टाइप सुरक्षा | Python टाइपिंग | Rust प्रकार प्रणाली |
| LLM एकीकरण | LangChain | AgentNode + ADK एजेंट्स |
पिछला: ← मल्टी-एजेंट सिस्टम्स | अगला: रीयलटाइम एजेंट्स →