1use async_trait::async_trait;
6
7use lellm_graph::node::StreamNodeResult;
8use lellm_graph::{FlowEvent, FlowNode, GraphError, NextStep, NodeOutput};
9use lellm_graph::{GraphEvent, SpanId, State, StateDelta, TerminalError};
10
11use crate::hook::{AgentHook, AgentHookContext, AgentHookSnapshot};
12use crate::runtime::{AgentEvent, ToolUseLoop, ToolUseResult};
13
14#[derive(Clone)]
35pub struct AgentFlowNode {
36 name: String,
38 loop_: ToolUseLoop,
40 message_key: String,
42 stream_events: bool,
44 hooks: Vec<std::sync::Arc<dyn AgentHook>>,
46}
47
48impl AgentFlowNode {
49 pub fn new(name: impl Into<String>, loop_: ToolUseLoop) -> Self {
51 Self {
52 name: name.into(),
53 loop_,
54 message_key: "messages".to_string(),
55 stream_events: true,
56 hooks: Vec::new(),
57 }
58 }
59
60 pub fn message_key(mut self, key: impl Into<String>) -> Self {
62 self.message_key = key.into();
63 self
64 }
65
66 pub fn stream_events(mut self, enabled: bool) -> Self {
68 self.stream_events = enabled;
69 self
70 }
71
72 pub fn hook(mut self, hook: impl AgentHook + 'static) -> Self {
76 self.hooks.push(std::sync::Arc::new(hook));
77 self
78 }
79
80 fn extract_messages(&self, state: &State) -> Vec<lellm_core::Message> {
82 if let Some(value) = state.get(&self.message_key) {
85 if let Some(arr) = value.as_array() {
87 let mut messages = Vec::new();
88 for v in arr {
89 if let Ok(msg) = serde_json::from_value::<lellm_core::Message>(v.clone()) {
90 messages.push(msg);
91 }
92 }
93 messages
94 } else if let Ok(msg) = serde_json::from_value::<lellm_core::Message>(value.clone()) {
95 vec![msg]
96 } else {
97 Vec::new()
98 }
99 } else {
100 Vec::new()
101 }
102 }
103
104 fn collect_deltas(&self, result: &ToolUseResult) -> Vec<StateDelta> {
106 let messages: Vec<serde_json::Value> = result
108 .messages
109 .iter()
110 .filter_map(|m| serde_json::to_value(m).ok())
111 .collect();
112
113 vec![
114 StateDelta::put(&self.message_key, serde_json::json!(messages)),
115 StateDelta::put(
116 format!("{}_stop_reason", self.name),
117 serde_json::json!(format!("{:?}", result.stop_reason)),
118 ),
119 StateDelta::put(
120 format!("{}_iterations", self.name),
121 serde_json::json!(result.iterations),
122 ),
123 StateDelta::put(
124 format!("{}_tool_calls", self.name),
125 serde_json::json!(result.tool_calls_executed),
126 ),
127 ]
128 }
129}
130
131#[async_trait]
132impl FlowNode for AgentFlowNode {
133 async fn execute(&self, state: &State) -> Result<NodeOutput, GraphError> {
135 let messages = self.extract_messages(state);
136
137 if messages.is_empty() {
139 tracing::debug!(
140 agent = %self.name,
141 "no input messages found in state key '{}'",
142 self.message_key
143 );
144 }
145
146 let ctx = AgentHookContext {
148 node_name: self.name.clone(),
149 input_message_count: messages.len(),
150 };
151 let mut hook_deltas: Vec<StateDelta> = self
152 .hooks
153 .iter()
154 .flat_map(|h| h.before_agent(&ctx))
155 .collect();
156
157 let result = self.loop_.execute(messages).await.map_err(|e| {
158 GraphError::Terminal(TerminalError::NodeExecutionFailed {
159 node: self.name.clone(),
160 source: e.into(),
161 })
162 })?;
163
164 let snapshot = AgentHookSnapshot {
166 result: result.clone(),
167 events: Vec::new(), };
169 hook_deltas.extend(self.hooks.iter().flat_map(|h| h.after_agent(&snapshot)));
170
171 let mut deltas = self.collect_deltas(&result);
172 deltas.extend(hook_deltas);
173
174 tracing::debug!(
175 agent = %self.name,
176 iterations = result.iterations,
177 tool_calls = result.tool_calls_executed,
178 stop_reason = ?result.stop_reason,
179 "agent execution completed"
180 );
181
182 Ok(NodeOutput {
183 deltas,
184 next: NextStep::GoToNext,
185 metadata: None,
186 })
187 }
188
189 async fn execute_stream(
191 &self,
192 state: &State,
193 sink: &tokio::sync::mpsc::Sender<GraphEvent>,
194 span_id: SpanId,
195 ) -> Result<StreamNodeResult, GraphError> {
196 let messages = self.extract_messages(state);
197
198 let ctx = AgentHookContext {
200 node_name: self.name.clone(),
201 input_message_count: messages.len(),
202 };
203 let hook_deltas: Vec<StateDelta> = self
204 .hooks
205 .iter()
206 .flat_map(|h| h.before_agent(&ctx))
207 .collect();
208
209 let _ = sink
211 .send(GraphEvent::Node {
212 span_id,
213 node_name: self.name.clone(),
214 event: FlowEvent::NodeStarted {
215 node_id: self.name.clone(),
216 span_id,
217 },
218 })
219 .await;
220
221 let mut agent_stream = self.loop_.execute_stream(messages);
223
224 let mut final_result: Option<ToolUseResult> = None;
225 let mut error_delta: Option<StateDelta> = None;
226 let mut events: Vec<AgentEvent> = Vec::new();
227
228 while let Some(agent_event) = agent_stream.recv().await {
229 let is_terminal = matches!(
231 &agent_event,
232 AgentEvent::LoopEnd { .. } | AgentEvent::LoopError { .. }
233 );
234
235 events.push(agent_event.clone());
237
238 if self.stream_events {
240 let _ = sink
241 .send(GraphEvent::Node {
242 span_id,
243 node_name: self.name.clone(),
244 event: FlowEvent::Custom {
245 node_id: self.name.clone(),
246 payload: Box::new(agent_event.clone()),
247 },
248 })
249 .await;
250 }
251
252 if is_terminal {
254 match &agent_event {
255 AgentEvent::LoopEnd { result } => {
256 final_result = Some(result.clone());
257 }
258 AgentEvent::LoopError { error, .. } => {
259 error_delta = Some(StateDelta::put(
261 format!("{}_error", self.name),
262 serde_json::json!(error.to_string()),
263 ));
264 }
265 _ => {}
266 }
267 }
268 }
269
270 if let Some(err_delta) = error_delta {
272 return Ok(StreamNodeResult::Fallback {
273 deltas: hook_deltas
274 .into_iter()
275 .chain(std::iter::once(err_delta))
276 .collect(),
277 reason: format!("agent loop error in '{}'", self.name),
278 node_name: self.name.clone(),
279 });
280 }
281
282 if let Some(result) = final_result {
284 let snapshot = AgentHookSnapshot {
286 result: result.clone(),
287 events,
288 };
289 let after_deltas: Vec<StateDelta> = self
290 .hooks
291 .iter()
292 .flat_map(|h| h.after_agent(&snapshot))
293 .collect();
294
295 let mut deltas = self.collect_deltas(&result);
296 deltas.extend(hook_deltas);
297 deltas.extend(after_deltas);
298
299 let _ = sink
301 .send(GraphEvent::Node {
302 span_id,
303 node_name: self.name.clone(),
304 event: FlowEvent::NodeCompleted {
305 node_id: self.name.clone(),
306 span_id,
307 duration: std::time::Duration::ZERO, },
309 })
310 .await;
311
312 return Ok(StreamNodeResult::Continue {
313 deltas,
314 next: NextStep::GoToNext,
315 span_id,
316 observed: None,
317 metadata: None,
318 });
319 }
320
321 Ok(StreamNodeResult::Fallback {
323 deltas: hook_deltas,
324 reason: "agent stream ended without terminal event".into(),
325 node_name: self.name.clone(),
326 })
327 }
328}