1use crate::checkpoint::Checkpointer;
6use crate::deferred::DeferredNodeConfig;
7use crate::edge::{END, Edge, EdgeTarget, START};
8use crate::error::{GraphError, Result};
9use crate::graph::{CompiledGraph, StateGraph};
10use crate::node::{ExecutionConfig, FunctionNode, Node, NodeContext, NodeOutput};
11use crate::state::{State, StateSchema};
12use crate::stream::{StreamEvent, StreamMode};
13use crate::timeout::TimeoutPolicy;
14use adk_core::{
15 Agent, AgentCapabilities, AgentRelationshipKind, AgentTopology, AgentTopologyMember,
16 AgentTopologyRelationship, Content, Event, EventStream, InvocationContext,
17};
18use async_trait::async_trait;
19use serde_json::json;
20use std::collections::HashMap;
21use std::future::Future;
22use std::pin::Pin;
23use std::sync::Arc;
24
25pub type BeforeAgentCallback = Arc<
27 dyn Fn(Arc<dyn InvocationContext>) -> Pin<Box<dyn Future<Output = adk_core::Result<()>> + Send>>
28 + Send
29 + Sync,
30>;
31
32pub type AfterAgentCallback = Arc<
33 dyn Fn(
34 Arc<dyn InvocationContext>,
35 Event,
36 ) -> Pin<Box<dyn Future<Output = adk_core::Result<()>> + Send>>
37 + Send
38 + Sync,
39>;
40
41pub type InputMapper = Arc<dyn Fn(&dyn InvocationContext) -> State + Send + Sync>;
43
44pub type OutputMapper = Arc<dyn Fn(&State) -> Vec<Event> + Send + Sync>;
46
47pub struct GraphAgent {
64 name: String,
65 description: String,
66 graph: Arc<CompiledGraph>,
67 input_mapper: InputMapper,
69 output_mapper: OutputMapper,
71 before_callback: Option<BeforeAgentCallback>,
73 after_callback: Option<AfterAgentCallback>,
75}
76
77impl GraphAgent {
78 pub fn builder(name: &str) -> GraphAgentBuilder {
80 GraphAgentBuilder::new(name)
81 }
82
83 pub fn from_graph(name: &str, graph: CompiledGraph) -> Self {
85 Self {
86 name: name.to_string(),
87 description: String::new(),
88 graph: Arc::new(graph),
89 input_mapper: Arc::new(default_input_mapper),
90 output_mapper: Arc::new(default_output_mapper),
91 before_callback: None,
92 after_callback: None,
93 }
94 }
95
96 #[cfg(feature = "action")]
101 pub fn from_workflow_schema(
102 name: &str,
103 schema: &crate::workflow::WorkflowSchema,
104 ) -> Result<Self> {
105 schema.build_graph(name)
106 }
107
108 pub fn graph(&self) -> &CompiledGraph {
110 &self.graph
111 }
112
113 pub async fn invoke(&self, input: State, config: ExecutionConfig) -> Result<State> {
115 self.graph.invoke(input, config).await
116 }
117
118 pub fn stream(
120 &self,
121 input: State,
122 config: ExecutionConfig,
123 mode: StreamMode,
124 ) -> impl futures::Stream<Item = Result<StreamEvent>> + '_ {
125 self.graph.stream(input, config, mode)
126 }
127}
128
129#[async_trait]
130impl Agent for GraphAgent {
131 fn name(&self) -> &str {
132 &self.name
133 }
134
135 fn description(&self) -> &str {
136 &self.description
137 }
138
139 fn sub_agents(&self) -> &[Arc<dyn Agent>] {
140 &[]
141 }
142
143 fn supports_agent_transfer(&self) -> bool {
144 false
145 }
146
147 fn capabilities(&self) -> AgentCapabilities {
148 AgentCapabilities {
149 checkpoint_resume: self.graph.has_checkpointer(),
150 shared_state: true,
151 invocation_metadata: true,
152 ..AgentCapabilities::default()
153 }
154 }
155
156 fn topology(&self) -> Option<AgentTopology> {
157 let mut nodes = self.graph.nodes.values().collect::<Vec<_>>();
158 nodes.sort_by_key(|node| node.name().to_string());
159 let mut members = vec![AgentTopologyMember {
160 name: self.name.clone(),
161 description: self.description.clone(),
162 coordinator: true,
163 capabilities: self.capabilities(),
164 }];
165 members.extend(nodes.into_iter().map(|node| AgentTopologyMember {
166 name: node.name().to_string(),
167 description: node.description().to_string(),
168 coordinator: false,
169 capabilities: node.capabilities(),
170 }));
171
172 let mut relationships = self
173 .graph
174 .get_entry_nodes()
175 .into_iter()
176 .map(|entry| AgentTopologyRelationship {
177 from: self.name.clone(),
178 to: entry,
179 kind: AgentRelationshipKind::Flow,
180 })
181 .collect::<Vec<_>>();
182 for edge in &self.graph.edges {
183 match edge {
184 Edge::Direct { source, target: EdgeTarget::Node(target) } => {
185 relationships.push(AgentTopologyRelationship {
186 from: source.clone(),
187 to: target.clone(),
188 kind: AgentRelationshipKind::Flow,
189 });
190 }
191 Edge::Conditional { source, targets, .. } => {
192 relationships.extend(targets.values().filter_map(|target| {
193 target.node_name().map(|target| AgentTopologyRelationship {
194 from: source.clone(),
195 to: target.to_string(),
196 kind: AgentRelationshipKind::Flow,
197 })
198 }));
199 }
200 Edge::Direct { target: EdgeTarget::End, .. } | Edge::Entry { .. } => {}
201 }
202 }
203 relationships.sort_by(|left, right| (&left.from, &left.to).cmp(&(&right.from, &right.to)));
204 relationships.dedup_by(|left, right| left.from == right.from && left.to == right.to);
205
206 Some(AgentTopology {
207 root: self.name.clone(),
208 coordinator: self.name.clone(),
209 members,
210 relationships,
211 })
212 }
213
214 async fn run(&self, ctx: Arc<dyn InvocationContext>) -> adk_core::Result<EventStream> {
215 if let Some(callback) = &self.before_callback {
217 callback(ctx.clone()).await?;
218 }
219
220 let input = (self.input_mapper)(ctx.as_ref());
222
223 let config = ExecutionConfig::new(ctx.session_id()).with_parent_context(ctx.clone());
227
228 let graph = self.graph.clone();
230 let output_mapper = self.output_mapper.clone();
231 let after_callback = self.after_callback.clone();
232 let ctx_clone = ctx.clone();
233
234 let stream = async_stream::stream! {
235 let mut executor =
239 crate::executor::PregelExecutor::new(&graph, config).restarting_completed_threads();
240 match executor.run(input).await {
241 Ok(state) => {
242 let events = output_mapper(&state);
243 for event in events {
244 if let Some(callback) = &after_callback
246 && let Err(e) = callback(ctx_clone.clone(), event.clone()).await {
247 yield Err(e);
248 return;
249 }
250 yield Ok(event);
251 }
252 }
253 Err(GraphError::Interrupted(interrupt)) => {
254 let tool_confirmation = crate::interrupt::GraphToolConfirmationPause::from_interrupted_execution(&interrupt);
259 let payload = tool_confirmation.clone().map_or_else(
260 || crate::interrupt::GraphInterruptPayload::new(
261 &interrupt.interrupt,
262 &interrupt.thread_id,
263 &interrupt.checkpoint_id,
264 ),
265 crate::interrupt::GraphInterruptPayload::from_tool_confirmation_pause,
266 );
267 let mut event = Event::new("graph_interrupted");
268 if let Some(pause) = tool_confirmation {
269 event.llm_response.interrupted = true;
270 event.llm_response.turn_complete = true;
271 event.actions.tool_confirmation = Some(pause.request);
272 }
273 event.set_content(
274 Content::new("assistant").with_text(interrupt.interrupt.to_string()),
275 );
276 event.provider_metadata.insert(
277 crate::interrupt::INTERRUPT_METADATA_KEY.to_string(),
278 payload.to_metadata_value(),
279 );
280 yield Ok(event);
281 }
282 Err(e) => {
283 yield Err(adk_core::AdkError::agent(e.to_string()));
284 }
285 }
286 };
287
288 Ok(Box::pin(stream))
289 }
290}
291
292fn default_input_mapper(ctx: &dyn InvocationContext) -> State {
294 let mut state = State::new();
295
296 let content = ctx.user_content();
298 let text: String = content.parts.iter().filter_map(|p| p.text()).collect::<Vec<_>>().join("\n");
299
300 if !text.is_empty() {
301 state.insert("input".to_string(), json!(text));
302 state.insert("messages".to_string(), json!([{"role": "user", "content": text}]));
303 }
304
305 state.insert("session_id".to_string(), json!(ctx.session_id()));
307
308 state
309}
310
311fn default_output_mapper(state: &State) -> Vec<Event> {
313 let mut events = Vec::new();
314
315 let output_text = state
317 .get("output")
318 .and_then(|v| v.as_str())
319 .or_else(|| state.get("result").and_then(|v| v.as_str()))
320 .or_else(|| {
321 state
322 .get("messages")
323 .and_then(|v| v.as_array())
324 .and_then(|arr| arr.last())
325 .and_then(|msg| msg.get("content"))
326 .and_then(|c| c.as_str())
327 });
328
329 let text = if let Some(text) = output_text {
330 text.to_string()
331 } else {
332 serde_json::to_string_pretty(state).unwrap_or_default()
334 };
335
336 let mut event = Event::new("graph_output");
337 event.set_content(Content::new("assistant").with_text(&text));
338 events.push(event);
339
340 events
341}
342
343pub struct GraphAgentBuilder {
345 name: String,
346 description: String,
347 schema: StateSchema,
348 nodes: Vec<Arc<dyn Node>>,
349 edges: Vec<Edge>,
350 checkpointer: Option<Arc<dyn Checkpointer>>,
351 interrupt_before: Vec<String>,
352 interrupt_after: Vec<String>,
353 recursion_limit: usize,
354 max_concurrency: Option<usize>,
355 input_mapper: Option<InputMapper>,
356 output_mapper: Option<OutputMapper>,
357 before_callback: Option<BeforeAgentCallback>,
358 after_callback: Option<AfterAgentCallback>,
359 timeout_policies: HashMap<String, TimeoutPolicy>,
360 default_timeout: Option<TimeoutPolicy>,
361 deferred_configs: HashMap<String, DeferredNodeConfig>,
362 #[cfg(feature = "node-cache")]
363 cache_policies: HashMap<String, crate::cache::NodeCachePolicy>,
364}
365
366impl GraphAgentBuilder {
367 pub fn new(name: &str) -> Self {
369 Self {
370 name: name.to_string(),
371 description: String::new(),
372 schema: StateSchema::simple(&["input", "output", "messages"]),
373 nodes: vec![],
374 edges: vec![],
375 checkpointer: None,
376 interrupt_before: vec![],
377 interrupt_after: vec![],
378 recursion_limit: 100,
379 max_concurrency: None,
380 input_mapper: None,
381 output_mapper: None,
382 before_callback: None,
383 after_callback: None,
384 timeout_policies: HashMap::new(),
385 default_timeout: None,
386 deferred_configs: HashMap::new(),
387 #[cfg(feature = "node-cache")]
388 cache_policies: HashMap::new(),
389 }
390 }
391
392 pub fn description(mut self, desc: &str) -> Self {
394 self.description = desc.to_string();
395 self
396 }
397
398 pub fn state_schema(mut self, schema: StateSchema) -> Self {
400 self.schema = schema;
401 self
402 }
403
404 pub fn channels(mut self, channels: &[&str]) -> Self {
406 self.schema = StateSchema::simple(channels);
407 self
408 }
409
410 pub fn node<N: Node + 'static>(mut self, node: N) -> Self {
412 self.nodes.push(Arc::new(node));
413 self
414 }
415
416 pub fn node_fn<F, Fut>(mut self, name: &str, func: F) -> Self
418 where
419 F: Fn(NodeContext) -> Fut + Send + Sync + 'static,
420 Fut: Future<Output = Result<NodeOutput>> + Send + 'static,
421 {
422 self.nodes.push(Arc::new(FunctionNode::new(name, func)));
423 self
424 }
425
426 pub fn edge(mut self, source: &str, target: &str) -> Self {
428 let target =
429 if target == END { EdgeTarget::End } else { EdgeTarget::Node(target.to_string()) };
430
431 if source == START {
432 let entry_idx = self.edges.iter().position(|e| matches!(e, Edge::Entry { .. }));
433 match entry_idx {
434 Some(idx) => {
435 if let Edge::Entry { targets } = &mut self.edges[idx]
436 && let EdgeTarget::Node(node) = &target
437 && !targets.contains(node)
438 {
439 targets.push(node.clone());
440 }
441 }
442 None => {
443 if let EdgeTarget::Node(node) = target {
444 self.edges.push(Edge::Entry { targets: vec![node] });
445 }
446 }
447 }
448 } else {
449 self.edges.push(Edge::Direct { source: source.to_string(), target });
450 }
451
452 self
453 }
454
455 pub fn conditional_edge<F, I>(mut self, source: &str, router: F, targets: I) -> Self
457 where
458 F: Fn(&State) -> String + Send + Sync + 'static,
459 I: IntoIterator<Item = (&'static str, &'static str)>,
460 {
461 let targets_map: HashMap<String, EdgeTarget> = targets
462 .into_iter()
463 .map(|(k, v)| {
464 let target =
465 if v == END { EdgeTarget::End } else { EdgeTarget::Node(v.to_string()) };
466 (k.to_string(), target)
467 })
468 .collect();
469
470 self.edges.push(Edge::Conditional {
471 source: source.to_string(),
472 router: Arc::new(router),
473 targets: targets_map,
474 });
475
476 self
477 }
478
479 pub fn checkpointer<C: Checkpointer + 'static>(mut self, checkpointer: C) -> Self {
481 self.checkpointer = Some(Arc::new(checkpointer));
482 self
483 }
484
485 pub fn checkpointer_arc(mut self, checkpointer: Arc<dyn Checkpointer>) -> Self {
487 self.checkpointer = Some(checkpointer);
488 self
489 }
490
491 pub fn interrupt_before(mut self, nodes: &[&str]) -> Self {
493 self.interrupt_before = nodes.iter().map(|s| s.to_string()).collect();
494 self
495 }
496
497 pub fn interrupt_after(mut self, nodes: &[&str]) -> Self {
499 self.interrupt_after = nodes.iter().map(|s| s.to_string()).collect();
500 self
501 }
502
503 pub fn max_concurrency(mut self, limit: usize) -> Self {
508 self.max_concurrency = Some(limit.max(1));
509 self
510 }
511
512 pub fn recursion_limit(mut self, limit: usize) -> Self {
513 self.recursion_limit = limit;
514 self
515 }
516
517 pub fn node_timeout(mut self, node_name: &str, policy: TimeoutPolicy) -> Self {
537 self.timeout_policies.insert(node_name.to_string(), policy);
538 self
539 }
540
541 pub fn default_timeout(mut self, policy: TimeoutPolicy) -> Self {
561 self.default_timeout = Some(policy);
562 self
563 }
564
565 pub fn mark_deferred(mut self, name: &str, config: DeferredNodeConfig) -> Self {
603 self.deferred_configs.insert(name.to_string(), config);
604 self
605 }
606
607 pub fn deferred_node<F, Fut>(mut self, name: &str, func: F, config: DeferredNodeConfig) -> Self
608 where
609 F: Fn(NodeContext) -> Fut + Send + Sync + 'static,
610 Fut: Future<Output = Result<NodeOutput>> + Send + 'static,
611 {
612 self.nodes.push(Arc::new(FunctionNode::new(name, func)));
613 self.deferred_configs.insert(name.to_string(), config);
614 self
615 }
616
617 #[cfg(feature = "node-cache")]
643 pub fn node_cache(mut self, name: &str, policy: crate::cache::NodeCachePolicy) -> Self {
644 self.cache_policies.insert(name.to_string(), policy);
645 self
646 }
647
648 pub fn input_mapper<F>(mut self, mapper: F) -> Self
650 where
651 F: Fn(&dyn InvocationContext) -> State + Send + Sync + 'static,
652 {
653 self.input_mapper = Some(Arc::new(mapper));
654 self
655 }
656
657 pub fn output_mapper<F>(mut self, mapper: F) -> Self
659 where
660 F: Fn(&State) -> Vec<Event> + Send + Sync + 'static,
661 {
662 self.output_mapper = Some(Arc::new(mapper));
663 self
664 }
665
666 pub fn before_agent_callback<F, Fut>(mut self, callback: F) -> Self
668 where
669 F: Fn(Arc<dyn InvocationContext>) -> Fut + Send + Sync + 'static,
670 Fut: Future<Output = adk_core::Result<()>> + Send + 'static,
671 {
672 self.before_callback = Some(Arc::new(move |ctx| Box::pin(callback(ctx))));
673 self
674 }
675
676 pub fn after_agent_callback<F, Fut>(mut self, callback: F) -> Self
680 where
681 F: Fn(Arc<dyn InvocationContext>, Event) -> Fut + Send + Sync + 'static,
682 Fut: Future<Output = adk_core::Result<()>> + Send + 'static,
683 {
684 self.after_callback = Some(Arc::new(move |ctx, event| {
685 let event_clone = event.clone();
686 Box::pin(callback(ctx, event_clone))
687 }));
688 self
689 }
690
691 #[cfg(feature = "action")]
697 pub fn action_node(mut self, config: adk_action::ActionNodeConfig) -> Self {
698 use crate::action::ActionNodeExecutor;
699
700 if let adk_action::ActionNodeConfig::Switch(ref switch_config) = config {
702 let conditions = switch_config.conditions.clone();
703 let eval_mode = switch_config.evaluation_mode.clone();
704 let default_branch = switch_config.default_branch.clone();
705 let source = config.standard().id.clone();
706
707 let mut targets_map: HashMap<String, EdgeTarget> = HashMap::new();
708 for condition in &conditions {
709 targets_map.insert(
710 condition.output_port.clone(),
711 EdgeTarget::Node(condition.output_port.clone()),
712 );
713 }
714 if let Some(ref default) = default_branch {
715 let target = if default == END {
716 EdgeTarget::End
717 } else {
718 EdgeTarget::Node(default.clone())
719 };
720 targets_map.insert(default.clone(), target);
721 }
722 targets_map.insert(END.to_string(), EdgeTarget::End);
723
724 let router = Arc::new(move |state: &State| -> String {
725 match crate::action::switch::evaluate_switch_conditions(
726 &conditions,
727 state,
728 &eval_mode,
729 default_branch.as_deref(),
730 ) {
731 Ok(ports) => ports.into_iter().next().unwrap_or_else(|| END.to_string()),
732 Err(_) => END.to_string(),
733 }
734 });
735
736 self.edges.push(Edge::Conditional { source, router, targets: targets_map });
737 }
738
739 let executor = ActionNodeExecutor::new(config);
740 self.nodes.push(Arc::new(executor));
741 self
742 }
743
744 pub fn build(self) -> Result<GraphAgent> {
746 let mut graph = StateGraph::new(self.schema);
748
749 for node in self.nodes {
751 graph.nodes.insert(node.name().to_string(), node);
752 }
753
754 graph.edges = self.edges;
756
757 let mut compiled = graph.compile()?;
759
760 if let Some(cp) = self.checkpointer {
762 compiled.checkpointer = Some(cp);
763 }
764 compiled.interrupt_before = self.interrupt_before.into_iter().collect();
765 compiled.interrupt_after = self.interrupt_after.into_iter().collect();
766 compiled.recursion_limit = self.recursion_limit;
767 compiled.max_concurrency = self.max_concurrency;
768 compiled.timeout_policies = self.timeout_policies;
769 compiled.default_timeout = self.default_timeout;
770 compiled.deferred_configs = self.deferred_configs;
771
772 #[cfg(feature = "node-cache")]
773 {
774 compiled.node_caches = self
775 .cache_policies
776 .iter()
777 .map(|(name, policy)| {
778 (name.clone(), Arc::new(crate::cache::NodeCache::from_policy(policy)))
779 })
780 .collect();
781 compiled.cache_policies = self.cache_policies;
782 }
783
784 Ok(GraphAgent {
785 name: self.name,
786 description: self.description,
787 graph: Arc::new(compiled),
788 input_mapper: self.input_mapper.unwrap_or(Arc::new(default_input_mapper)),
789 output_mapper: self.output_mapper.unwrap_or(Arc::new(default_output_mapper)),
790 before_callback: self.before_callback,
791 after_callback: self.after_callback,
792 })
793 }
794}
795
796#[cfg(test)]
797mod tests {
798 use super::*;
799 use serde_json::json;
800
801 #[tokio::test]
802 async fn test_graph_agent_builder() {
803 let agent = GraphAgent::builder("test")
804 .description("Test agent")
805 .channels(&["value"])
806 .node_fn("set", |_ctx| async { Ok(NodeOutput::new().with_update("value", json!(42))) })
807 .edge(START, "set")
808 .edge("set", END)
809 .build()
810 .unwrap();
811
812 assert_eq!(agent.name(), "test");
813 assert_eq!(agent.description(), "Test agent");
814
815 let result = agent.invoke(State::new(), ExecutionConfig::new("test")).await.unwrap();
817
818 assert_eq!(result.get("value"), Some(&json!(42)));
819
820 let topology = agent.topology().expect("graph topology");
821 assert_eq!(topology.root, "test");
822 assert_eq!(topology.coordinator, "test");
823 assert_eq!(topology.members.len(), 2);
824 assert_eq!(topology.relationships.len(), 1);
825 assert_eq!(topology.relationships[0].from, "test");
826 assert_eq!(topology.relationships[0].to, "set");
827 assert_eq!(topology.relationships[0].kind, AgentRelationshipKind::Flow);
828 }
829}