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::{Agent, Content, Event, EventStream, InvocationContext};
15use async_trait::async_trait;
16use serde_json::json;
17use std::collections::HashMap;
18use std::future::Future;
19use std::pin::Pin;
20use std::sync::Arc;
21
22pub type BeforeAgentCallback = Arc<
24 dyn Fn(Arc<dyn InvocationContext>) -> Pin<Box<dyn Future<Output = adk_core::Result<()>> + Send>>
25 + Send
26 + Sync,
27>;
28
29pub type AfterAgentCallback = Arc<
30 dyn Fn(
31 Arc<dyn InvocationContext>,
32 Event,
33 ) -> Pin<Box<dyn Future<Output = adk_core::Result<()>> + Send>>
34 + Send
35 + Sync,
36>;
37
38pub type InputMapper = Arc<dyn Fn(&dyn InvocationContext) -> State + Send + Sync>;
40
41pub type OutputMapper = Arc<dyn Fn(&State) -> Vec<Event> + Send + Sync>;
43
44pub struct GraphAgent {
46 name: String,
47 description: String,
48 graph: Arc<CompiledGraph>,
49 input_mapper: InputMapper,
51 output_mapper: OutputMapper,
53 before_callback: Option<BeforeAgentCallback>,
55 after_callback: Option<AfterAgentCallback>,
57}
58
59impl GraphAgent {
60 pub fn builder(name: &str) -> GraphAgentBuilder {
62 GraphAgentBuilder::new(name)
63 }
64
65 pub fn from_graph(name: &str, graph: CompiledGraph) -> Self {
67 Self {
68 name: name.to_string(),
69 description: String::new(),
70 graph: Arc::new(graph),
71 input_mapper: Arc::new(default_input_mapper),
72 output_mapper: Arc::new(default_output_mapper),
73 before_callback: None,
74 after_callback: None,
75 }
76 }
77
78 #[cfg(feature = "action")]
83 pub fn from_workflow_schema(
84 name: &str,
85 schema: &crate::workflow::WorkflowSchema,
86 ) -> Result<Self> {
87 schema.build_graph(name)
88 }
89
90 pub fn graph(&self) -> &CompiledGraph {
92 &self.graph
93 }
94
95 pub async fn invoke(&self, input: State, config: ExecutionConfig) -> Result<State> {
97 self.graph.invoke(input, config).await
98 }
99
100 pub fn stream(
102 &self,
103 input: State,
104 config: ExecutionConfig,
105 mode: StreamMode,
106 ) -> impl futures::Stream<Item = Result<StreamEvent>> + '_ {
107 self.graph.stream(input, config, mode)
108 }
109}
110
111#[async_trait]
112impl Agent for GraphAgent {
113 fn name(&self) -> &str {
114 &self.name
115 }
116
117 fn description(&self) -> &str {
118 &self.description
119 }
120
121 fn sub_agents(&self) -> &[Arc<dyn Agent>] {
122 &[]
123 }
124
125 async fn run(&self, ctx: Arc<dyn InvocationContext>) -> adk_core::Result<EventStream> {
126 if let Some(callback) = &self.before_callback {
128 callback(ctx.clone()).await?;
129 }
130
131 let input = (self.input_mapper)(ctx.as_ref());
133
134 let config = ExecutionConfig::new(ctx.session_id()).with_parent_context(ctx.clone());
138
139 let graph = self.graph.clone();
141 let output_mapper = self.output_mapper.clone();
142 let after_callback = self.after_callback.clone();
143 let ctx_clone = ctx.clone();
144
145 let stream = async_stream::stream! {
146 match graph.invoke(input, config).await {
147 Ok(state) => {
148 let events = output_mapper(&state);
149 for event in events {
150 if let Some(callback) = &after_callback
152 && let Err(e) = callback(ctx_clone.clone(), event.clone()).await {
153 yield Err(e);
154 return;
155 }
156 yield Ok(event);
157 }
158 }
159 Err(GraphError::Interrupted(interrupt)) => {
160 let payload = crate::interrupt::GraphInterruptPayload::new(
165 &interrupt.interrupt,
166 &interrupt.thread_id,
167 &interrupt.checkpoint_id,
168 );
169 let mut event = Event::new("graph_interrupted");
170 event.set_content(
171 Content::new("assistant").with_text(interrupt.interrupt.to_string()),
172 );
173 event.provider_metadata.insert(
174 crate::interrupt::INTERRUPT_METADATA_KEY.to_string(),
175 payload.to_metadata_value(),
176 );
177 yield Ok(event);
178 }
179 Err(e) => {
180 yield Err(adk_core::AdkError::agent(e.to_string()));
181 }
182 }
183 };
184
185 Ok(Box::pin(stream))
186 }
187}
188
189fn default_input_mapper(ctx: &dyn InvocationContext) -> State {
191 let mut state = State::new();
192
193 let content = ctx.user_content();
195 let text: String = content.parts.iter().filter_map(|p| p.text()).collect::<Vec<_>>().join("\n");
196
197 if !text.is_empty() {
198 state.insert("input".to_string(), json!(text));
199 state.insert("messages".to_string(), json!([{"role": "user", "content": text}]));
200 }
201
202 state.insert("session_id".to_string(), json!(ctx.session_id()));
204
205 state
206}
207
208fn default_output_mapper(state: &State) -> Vec<Event> {
210 let mut events = Vec::new();
211
212 let output_text = state
214 .get("output")
215 .and_then(|v| v.as_str())
216 .or_else(|| state.get("result").and_then(|v| v.as_str()))
217 .or_else(|| {
218 state
219 .get("messages")
220 .and_then(|v| v.as_array())
221 .and_then(|arr| arr.last())
222 .and_then(|msg| msg.get("content"))
223 .and_then(|c| c.as_str())
224 });
225
226 let text = if let Some(text) = output_text {
227 text.to_string()
228 } else {
229 serde_json::to_string_pretty(state).unwrap_or_default()
231 };
232
233 let mut event = Event::new("graph_output");
234 event.set_content(Content::new("assistant").with_text(&text));
235 events.push(event);
236
237 events
238}
239
240pub struct GraphAgentBuilder {
242 name: String,
243 description: String,
244 schema: StateSchema,
245 nodes: Vec<Arc<dyn Node>>,
246 edges: Vec<Edge>,
247 checkpointer: Option<Arc<dyn Checkpointer>>,
248 interrupt_before: Vec<String>,
249 interrupt_after: Vec<String>,
250 recursion_limit: usize,
251 max_concurrency: Option<usize>,
252 input_mapper: Option<InputMapper>,
253 output_mapper: Option<OutputMapper>,
254 before_callback: Option<BeforeAgentCallback>,
255 after_callback: Option<AfterAgentCallback>,
256 timeout_policies: HashMap<String, TimeoutPolicy>,
257 default_timeout: Option<TimeoutPolicy>,
258 deferred_configs: HashMap<String, DeferredNodeConfig>,
259 #[cfg(feature = "node-cache")]
260 cache_policies: HashMap<String, crate::cache::NodeCachePolicy>,
261}
262
263impl GraphAgentBuilder {
264 pub fn new(name: &str) -> Self {
266 Self {
267 name: name.to_string(),
268 description: String::new(),
269 schema: StateSchema::simple(&["input", "output", "messages"]),
270 nodes: vec![],
271 edges: vec![],
272 checkpointer: None,
273 interrupt_before: vec![],
274 interrupt_after: vec![],
275 recursion_limit: 100,
276 max_concurrency: None,
277 input_mapper: None,
278 output_mapper: None,
279 before_callback: None,
280 after_callback: None,
281 timeout_policies: HashMap::new(),
282 default_timeout: None,
283 deferred_configs: HashMap::new(),
284 #[cfg(feature = "node-cache")]
285 cache_policies: HashMap::new(),
286 }
287 }
288
289 pub fn description(mut self, desc: &str) -> Self {
291 self.description = desc.to_string();
292 self
293 }
294
295 pub fn state_schema(mut self, schema: StateSchema) -> Self {
297 self.schema = schema;
298 self
299 }
300
301 pub fn channels(mut self, channels: &[&str]) -> Self {
303 self.schema = StateSchema::simple(channels);
304 self
305 }
306
307 pub fn node<N: Node + 'static>(mut self, node: N) -> Self {
309 self.nodes.push(Arc::new(node));
310 self
311 }
312
313 pub fn node_fn<F, Fut>(mut self, name: &str, func: F) -> Self
315 where
316 F: Fn(NodeContext) -> Fut + Send + Sync + 'static,
317 Fut: Future<Output = Result<NodeOutput>> + Send + 'static,
318 {
319 self.nodes.push(Arc::new(FunctionNode::new(name, func)));
320 self
321 }
322
323 pub fn edge(mut self, source: &str, target: &str) -> Self {
325 let target =
326 if target == END { EdgeTarget::End } else { EdgeTarget::Node(target.to_string()) };
327
328 if source == START {
329 let entry_idx = self.edges.iter().position(|e| matches!(e, Edge::Entry { .. }));
330 match entry_idx {
331 Some(idx) => {
332 if let Edge::Entry { targets } = &mut self.edges[idx]
333 && let EdgeTarget::Node(node) = &target
334 && !targets.contains(node)
335 {
336 targets.push(node.clone());
337 }
338 }
339 None => {
340 if let EdgeTarget::Node(node) = target {
341 self.edges.push(Edge::Entry { targets: vec![node] });
342 }
343 }
344 }
345 } else {
346 self.edges.push(Edge::Direct { source: source.to_string(), target });
347 }
348
349 self
350 }
351
352 pub fn conditional_edge<F, I>(mut self, source: &str, router: F, targets: I) -> Self
354 where
355 F: Fn(&State) -> String + Send + Sync + 'static,
356 I: IntoIterator<Item = (&'static str, &'static str)>,
357 {
358 let targets_map: HashMap<String, EdgeTarget> = targets
359 .into_iter()
360 .map(|(k, v)| {
361 let target =
362 if v == END { EdgeTarget::End } else { EdgeTarget::Node(v.to_string()) };
363 (k.to_string(), target)
364 })
365 .collect();
366
367 self.edges.push(Edge::Conditional {
368 source: source.to_string(),
369 router: Arc::new(router),
370 targets: targets_map,
371 });
372
373 self
374 }
375
376 pub fn checkpointer<C: Checkpointer + 'static>(mut self, checkpointer: C) -> Self {
378 self.checkpointer = Some(Arc::new(checkpointer));
379 self
380 }
381
382 pub fn checkpointer_arc(mut self, checkpointer: Arc<dyn Checkpointer>) -> Self {
384 self.checkpointer = Some(checkpointer);
385 self
386 }
387
388 pub fn interrupt_before(mut self, nodes: &[&str]) -> Self {
390 self.interrupt_before = nodes.iter().map(|s| s.to_string()).collect();
391 self
392 }
393
394 pub fn interrupt_after(mut self, nodes: &[&str]) -> Self {
396 self.interrupt_after = nodes.iter().map(|s| s.to_string()).collect();
397 self
398 }
399
400 pub fn max_concurrency(mut self, limit: usize) -> Self {
405 self.max_concurrency = Some(limit.max(1));
406 self
407 }
408
409 pub fn recursion_limit(mut self, limit: usize) -> Self {
410 self.recursion_limit = limit;
411 self
412 }
413
414 pub fn node_timeout(mut self, node_name: &str, policy: TimeoutPolicy) -> Self {
434 self.timeout_policies.insert(node_name.to_string(), policy);
435 self
436 }
437
438 pub fn default_timeout(mut self, policy: TimeoutPolicy) -> Self {
458 self.default_timeout = Some(policy);
459 self
460 }
461
462 pub fn mark_deferred(mut self, name: &str, config: DeferredNodeConfig) -> Self {
500 self.deferred_configs.insert(name.to_string(), config);
501 self
502 }
503
504 pub fn deferred_node<F, Fut>(mut self, name: &str, func: F, config: DeferredNodeConfig) -> Self
505 where
506 F: Fn(NodeContext) -> Fut + Send + Sync + 'static,
507 Fut: Future<Output = Result<NodeOutput>> + Send + 'static,
508 {
509 self.nodes.push(Arc::new(FunctionNode::new(name, func)));
510 self.deferred_configs.insert(name.to_string(), config);
511 self
512 }
513
514 #[cfg(feature = "node-cache")]
540 pub fn node_cache(mut self, name: &str, policy: crate::cache::NodeCachePolicy) -> Self {
541 self.cache_policies.insert(name.to_string(), policy);
542 self
543 }
544
545 pub fn input_mapper<F>(mut self, mapper: F) -> Self
547 where
548 F: Fn(&dyn InvocationContext) -> State + Send + Sync + 'static,
549 {
550 self.input_mapper = Some(Arc::new(mapper));
551 self
552 }
553
554 pub fn output_mapper<F>(mut self, mapper: F) -> Self
556 where
557 F: Fn(&State) -> Vec<Event> + Send + Sync + 'static,
558 {
559 self.output_mapper = Some(Arc::new(mapper));
560 self
561 }
562
563 pub fn before_agent_callback<F, Fut>(mut self, callback: F) -> Self
565 where
566 F: Fn(Arc<dyn InvocationContext>) -> Fut + Send + Sync + 'static,
567 Fut: Future<Output = adk_core::Result<()>> + Send + 'static,
568 {
569 self.before_callback = Some(Arc::new(move |ctx| Box::pin(callback(ctx))));
570 self
571 }
572
573 pub fn after_agent_callback<F, Fut>(mut self, callback: F) -> Self
577 where
578 F: Fn(Arc<dyn InvocationContext>, Event) -> Fut + Send + Sync + 'static,
579 Fut: Future<Output = adk_core::Result<()>> + Send + 'static,
580 {
581 self.after_callback = Some(Arc::new(move |ctx, event| {
582 let event_clone = event.clone();
583 Box::pin(callback(ctx, event_clone))
584 }));
585 self
586 }
587
588 #[cfg(feature = "action")]
594 pub fn action_node(mut self, config: adk_action::ActionNodeConfig) -> Self {
595 use crate::action::ActionNodeExecutor;
596
597 if let adk_action::ActionNodeConfig::Switch(ref switch_config) = config {
599 let conditions = switch_config.conditions.clone();
600 let eval_mode = switch_config.evaluation_mode.clone();
601 let default_branch = switch_config.default_branch.clone();
602 let source = config.standard().id.clone();
603
604 let mut targets_map: HashMap<String, EdgeTarget> = HashMap::new();
605 for condition in &conditions {
606 targets_map.insert(
607 condition.output_port.clone(),
608 EdgeTarget::Node(condition.output_port.clone()),
609 );
610 }
611 if let Some(ref default) = default_branch {
612 let target = if default == END {
613 EdgeTarget::End
614 } else {
615 EdgeTarget::Node(default.clone())
616 };
617 targets_map.insert(default.clone(), target);
618 }
619 targets_map.insert(END.to_string(), EdgeTarget::End);
620
621 let router = Arc::new(move |state: &State| -> String {
622 match crate::action::switch::evaluate_switch_conditions(
623 &conditions,
624 state,
625 &eval_mode,
626 default_branch.as_deref(),
627 ) {
628 Ok(ports) => ports.into_iter().next().unwrap_or_else(|| END.to_string()),
629 Err(_) => END.to_string(),
630 }
631 });
632
633 self.edges.push(Edge::Conditional { source, router, targets: targets_map });
634 }
635
636 let executor = ActionNodeExecutor::new(config);
637 self.nodes.push(Arc::new(executor));
638 self
639 }
640
641 pub fn build(self) -> Result<GraphAgent> {
643 let mut graph = StateGraph::new(self.schema);
645
646 for node in self.nodes {
648 graph.nodes.insert(node.name().to_string(), node);
649 }
650
651 graph.edges = self.edges;
653
654 let mut compiled = graph.compile()?;
656
657 if let Some(cp) = self.checkpointer {
659 compiled.checkpointer = Some(cp);
660 }
661 compiled.interrupt_before = self.interrupt_before.into_iter().collect();
662 compiled.interrupt_after = self.interrupt_after.into_iter().collect();
663 compiled.recursion_limit = self.recursion_limit;
664 compiled.max_concurrency = self.max_concurrency;
665 compiled.timeout_policies = self.timeout_policies;
666 compiled.default_timeout = self.default_timeout;
667 compiled.deferred_configs = self.deferred_configs;
668
669 #[cfg(feature = "node-cache")]
670 {
671 compiled.cache_policies = self.cache_policies;
672 }
673
674 Ok(GraphAgent {
675 name: self.name,
676 description: self.description,
677 graph: Arc::new(compiled),
678 input_mapper: self.input_mapper.unwrap_or(Arc::new(default_input_mapper)),
679 output_mapper: self.output_mapper.unwrap_or(Arc::new(default_output_mapper)),
680 before_callback: self.before_callback,
681 after_callback: self.after_callback,
682 })
683 }
684}
685
686#[cfg(test)]
687mod tests {
688 use super::*;
689 use serde_json::json;
690
691 #[tokio::test]
692 async fn test_graph_agent_builder() {
693 let agent = GraphAgent::builder("test")
694 .description("Test agent")
695 .channels(&["value"])
696 .node_fn("set", |_ctx| async { Ok(NodeOutput::new().with_update("value", json!(42))) })
697 .edge(START, "set")
698 .edge("set", END)
699 .build()
700 .unwrap();
701
702 assert_eq!(agent.name(), "test");
703 assert_eq!(agent.description(), "Test agent");
704
705 let result = agent.invoke(State::new(), ExecutionConfig::new("test")).await.unwrap();
707
708 assert_eq!(result.get("value"), Some(&json!(42)));
709 }
710}