adk_agent/workflow/
loop_agent.rs1#[cfg(feature = "skills")]
2use crate::skill_shim::load_skill_index;
3use crate::skill_shim::{SelectionPolicy, SkillIndex};
4use adk_core::{
5 AfterAgentCallback, Agent, BeforeAgentCallback, CallbackContext, Content, Event, EventStream,
6 InvocationContext, ReadonlyContext, Result, Session, State,
7};
8use async_stream::stream;
9use async_trait::async_trait;
10use std::collections::HashMap;
11use std::sync::{Arc, RwLock};
12
13pub const DEFAULT_LOOP_MAX_ITERATIONS: u32 = 1000;
16
17pub struct LoopAgent {
19 name: String,
20 description: String,
21 sub_agents: Vec<Arc<dyn Agent>>,
22 max_iterations: u32,
23 skills_index: Option<Arc<SkillIndex>>,
24 skill_policy: SelectionPolicy,
25 max_skill_chars: usize,
26 before_callbacks: Arc<Vec<BeforeAgentCallback>>,
27 after_callbacks: Arc<Vec<AfterAgentCallback>>,
28}
29
30impl LoopAgent {
31 pub fn new(name: impl Into<String>, sub_agents: Vec<Arc<dyn Agent>>) -> Self {
33 Self {
34 name: name.into(),
35 description: String::new(),
36 sub_agents,
37 max_iterations: DEFAULT_LOOP_MAX_ITERATIONS,
38 skills_index: None,
39 skill_policy: SelectionPolicy::default(),
40 max_skill_chars: 2000,
41 before_callbacks: Arc::new(Vec::new()),
42 after_callbacks: Arc::new(Vec::new()),
43 }
44 }
45
46 pub fn with_description(mut self, desc: impl Into<String>) -> Self {
48 self.description = desc.into();
49 self
50 }
51
52 pub fn with_max_iterations(mut self, max: u32) -> Self {
54 self.max_iterations = max;
55 self
56 }
57
58 #[cfg(feature = "skills")]
60 pub fn with_skills(mut self, index: SkillIndex) -> Self {
61 self.skills_index = Some(Arc::new(index));
62 self
63 }
64
65 #[cfg(feature = "skills")]
67 pub fn with_auto_skills(self) -> Result<Self> {
68 self.with_skills_from_root(".")
69 }
70
71 #[cfg(feature = "skills")]
73 pub fn with_skills_from_root(mut self, root: impl AsRef<std::path::Path>) -> Result<Self> {
74 let index = load_skill_index(root).map_err(|e| adk_core::AdkError::agent(e.to_string()))?;
75 self.skills_index = Some(Arc::new(index));
76 Ok(self)
77 }
78
79 #[cfg(feature = "skills")]
81 pub fn with_skill_policy(mut self, policy: SelectionPolicy) -> Self {
82 self.skill_policy = policy;
83 self
84 }
85
86 #[cfg(feature = "skills")]
88 pub fn with_skill_budget(mut self, max_chars: usize) -> Self {
89 self.max_skill_chars = max_chars;
90 self
91 }
92
93 pub fn before_callback(mut self, callback: BeforeAgentCallback) -> Self {
95 if let Some(callbacks) = Arc::get_mut(&mut self.before_callbacks) {
96 callbacks.push(callback);
97 }
98 self
99 }
100
101 pub fn after_callback(mut self, callback: AfterAgentCallback) -> Self {
103 if let Some(callbacks) = Arc::get_mut(&mut self.after_callbacks) {
104 callbacks.push(callback);
105 }
106 self
107 }
108}
109
110struct HistoryTrackingSession {
111 parent_ctx: Arc<dyn InvocationContext>,
112 history: Arc<RwLock<Vec<Content>>>,
113 state: StateTrackingState,
114}
115
116struct StateTrackingState {
117 values: RwLock<HashMap<String, serde_json::Value>>,
118}
119
120impl StateTrackingState {
121 fn new(parent_ctx: &Arc<dyn InvocationContext>) -> Self {
122 Self { values: RwLock::new(parent_ctx.session().state().all()) }
123 }
124
125 fn apply_delta(&self, delta: &HashMap<String, serde_json::Value>) {
126 if delta.is_empty() {
127 return;
128 }
129
130 let mut values = self.values.write().unwrap_or_else(|e| e.into_inner());
131 for (key, value) in delta {
132 values.insert(key.clone(), value.clone());
133 }
134 }
135}
136
137impl State for StateTrackingState {
138 fn get(&self, key: &str) -> Option<serde_json::Value> {
139 self.values.read().unwrap_or_else(|e| e.into_inner()).get(key).cloned()
140 }
141
142 fn set(&mut self, key: String, value: serde_json::Value) {
143 if let Err(msg) = adk_core::validate_state_key(&key) {
144 tracing::warn!(key = %key, "rejecting invalid state key: {msg}");
145 return;
146 }
147 self.values.write().unwrap_or_else(|e| e.into_inner()).insert(key, value);
148 }
149
150 fn all(&self) -> HashMap<String, serde_json::Value> {
151 self.values.read().unwrap_or_else(|e| e.into_inner()).clone()
152 }
153}
154
155impl HistoryTrackingSession {
156 fn new(parent_ctx: Arc<dyn InvocationContext>) -> Self {
157 Self {
158 history: Arc::new(RwLock::new(parent_ctx.session().conversation_history())),
159 state: StateTrackingState::new(&parent_ctx),
160 parent_ctx,
161 }
162 }
163
164 fn apply_event(&self, event: &Event) {
165 if let Some(content) = &event.llm_response.content {
166 let mut history = self.history.write().unwrap_or_else(|e| e.into_inner());
171
172 if event.llm_response.partial {
173 if let Some(last) = history.last_mut()
175 && last.role == content.role
176 {
177 for part in &content.parts {
178 if let adk_core::Part::Text { text } = part {
179 if let Some(adk_core::Part::Text { text: existing }) =
181 last.parts.last_mut()
182 {
183 existing.push_str(text);
184 } else {
185 last.parts.push(part.clone());
186 }
187 } else {
188 last.parts.push(part.clone());
189 }
190 }
191 return;
192 }
193 history.push(content.clone());
195 } else {
196 if let Some(last) = history.last_mut() {
203 if last.role == content.role && !content.parts.is_empty() {
204 for part in &content.parts {
206 if let adk_core::Part::Text { text } = part {
207 if let Some(adk_core::Part::Text { text: existing }) =
208 last.parts.last_mut()
209 {
210 existing.push_str(text);
211 } else {
212 last.parts.push(part.clone());
213 }
214 } else {
215 last.parts.push(part.clone());
216 }
217 }
218 } else if !content.parts.is_empty() {
219 history.push(content.clone());
220 }
221 } else {
222 history.push(content.clone());
223 }
224 }
225 }
226 self.state.apply_delta(&event.actions.state_delta);
227 }
228}
229
230impl Session for HistoryTrackingSession {
231 fn id(&self) -> &str {
232 self.parent_ctx.session().id()
233 }
234
235 fn app_name(&self) -> &str {
236 self.parent_ctx.session().app_name()
237 }
238
239 fn user_id(&self) -> &str {
240 self.parent_ctx.session().user_id()
241 }
242
243 fn state(&self) -> &dyn State {
244 &self.state
245 }
246
247 fn conversation_history(&self) -> Vec<Content> {
248 self.history.read().unwrap_or_else(|e| e.into_inner()).clone()
249 }
250
251 fn conversation_history_for_agent(&self, _agent_name: &str) -> Vec<Content> {
252 self.conversation_history()
253 }
254
255 fn append_to_history(&self, content: Content) {
256 self.history.write().unwrap_or_else(|e| e.into_inner()).push(content);
257 }
258}
259
260#[cfg(test)]
262pub(crate) fn history_tracking_context_for_test(
263 parent: Arc<dyn InvocationContext>,
264) -> Arc<dyn InvocationContext> {
265 Arc::new(HistoryTrackingContext::new(parent))
266}
267
268struct HistoryTrackingContext {
269 parent_ctx: Arc<dyn InvocationContext>,
270 session: HistoryTrackingSession,
271}
272
273impl HistoryTrackingContext {
274 fn new(parent_ctx: Arc<dyn InvocationContext>) -> Self {
275 let session = HistoryTrackingSession::new(parent_ctx.clone());
276 Self { parent_ctx, session }
277 }
278
279 fn apply_event(&self, event: &Event) {
280 self.session.apply_event(event);
281 }
282}
283
284#[async_trait]
285impl adk_core::ReadonlyContext for HistoryTrackingContext {
286 fn invocation_id(&self) -> &str {
287 self.parent_ctx.invocation_id()
288 }
289
290 fn agent_name(&self) -> &str {
291 self.parent_ctx.agent_name()
292 }
293
294 fn user_id(&self) -> &str {
295 self.parent_ctx.user_id()
296 }
297
298 fn app_name(&self) -> &str {
299 self.parent_ctx.app_name()
300 }
301
302 fn session_id(&self) -> &str {
303 self.parent_ctx.session_id()
304 }
305
306 fn branch(&self) -> &str {
307 self.parent_ctx.branch()
308 }
309
310 fn user_content(&self) -> &Content {
311 self.parent_ctx.user_content()
312 }
313}
314
315#[async_trait]
316impl CallbackContext for HistoryTrackingContext {
317 fn artifacts(&self) -> Option<Arc<dyn adk_core::Artifacts>> {
318 self.parent_ctx.artifacts()
319 }
320
321 fn shared_state(&self) -> Option<Arc<adk_core::SharedState>> {
322 self.parent_ctx.shared_state()
323 }
324}
325
326#[async_trait]
327impl InvocationContext for HistoryTrackingContext {
328 fn agent(&self) -> Arc<dyn Agent> {
329 self.parent_ctx.agent()
330 }
331
332 fn memory(&self) -> Option<Arc<dyn adk_core::Memory>> {
333 self.parent_ctx.memory()
334 }
335
336 fn session(&self) -> &dyn Session {
337 &self.session
338 }
339
340 fn run_config(&self) -> &adk_core::RunConfig {
341 self.parent_ctx.run_config()
342 }
343
344 fn end_invocation(&self) {
345 self.parent_ctx.end_invocation();
346 }
347
348 fn ended(&self) -> bool {
349 self.parent_ctx.ended()
350 }
351
352 fn is_cancelled(&self) -> bool {
353 self.parent_ctx.is_cancelled()
354 }
355
356 fn user_scopes(&self) -> Vec<String> {
357 self.parent_ctx.user_scopes()
358 }
359
360 fn request_metadata(&self) -> HashMap<String, serde_json::Value> {
361 self.parent_ctx.request_metadata()
362 }
363
364 async fn get_secret(&self, name: &str) -> adk_core::Result<Option<String>> {
365 self.parent_ctx.get_secret(name).await
366 }
367
368 async fn get_secret_for(
369 &self,
370 request: &adk_core::SecretRequest,
371 ) -> adk_core::Result<Option<String>> {
372 self.parent_ctx.get_secret_for(request).await
373 }
374}
375
376#[async_trait]
377impl Agent for LoopAgent {
378 fn name(&self) -> &str {
379 &self.name
380 }
381
382 fn description(&self) -> &str {
383 &self.description
384 }
385
386 fn sub_agents(&self) -> &[Arc<dyn Agent>] {
387 &self.sub_agents
388 }
389
390 fn supports_agent_transfer(&self) -> bool {
391 false
395 }
396
397 async fn run(&self, ctx: Arc<dyn InvocationContext>) -> Result<EventStream> {
398 let sub_agents = self.sub_agents.clone();
399 let max_iterations = self.max_iterations;
400 let before_callbacks = self.before_callbacks.clone();
401 let after_callbacks = self.after_callbacks.clone();
402 let agent_name = self.name.clone();
403 let run_ctx = super::skill_context::with_skill_injected_context(
404 ctx,
405 self.skills_index.as_ref(),
406 &self.skill_policy,
407 self.max_skill_chars,
408 );
409 let run_ctx = Arc::new(HistoryTrackingContext::new(run_ctx));
410
411 let s = stream! {
412 use futures::StreamExt;
413
414 for callback in before_callbacks.as_ref() {
416 match callback(run_ctx.clone() as Arc<dyn CallbackContext>).await {
417 Ok(Some(content)) => {
418 let mut early_event = Event::new(run_ctx.invocation_id());
419 early_event.author = agent_name.clone();
420 early_event.llm_response.content = Some(content);
421 yield Ok(early_event);
422
423 for after_cb in after_callbacks.as_ref() {
424 match after_cb(run_ctx.clone() as Arc<dyn CallbackContext>).await {
425 Ok(Some(after_content)) => {
426 let mut after_event = Event::new(run_ctx.invocation_id());
427 after_event.author = agent_name.clone();
428 after_event.llm_response.content = Some(after_content);
429 yield Ok(after_event);
430 return;
431 }
432 Ok(None) => continue,
433 Err(e) => { yield Err(e); return; }
434 }
435 }
436 return;
437 }
438 Ok(None) => continue,
439 Err(e) => { yield Err(e); return; }
440 }
441 }
442
443 let mut remaining = max_iterations;
444
445 loop {
446 let mut should_exit = false;
447
448 for agent in &sub_agents {
449 let mut stream = agent.run(run_ctx.clone() as Arc<dyn InvocationContext>).await?;
450
451 while let Some(result) = stream.next().await {
452 match result {
453 Ok(event) => {
454 run_ctx.apply_event(&event);
455 if event.actions.escalate {
456 should_exit = true;
457 }
458 yield Ok(event);
459 }
460 Err(e) => {
461 yield Err(e);
462 return;
463 }
464 }
465 }
466
467 if should_exit {
468 break;
469 }
470 }
471
472 if should_exit {
473 break;
474 }
475
476 remaining -= 1;
477 if remaining == 0 {
478 break;
479 }
480 }
481
482 for callback in after_callbacks.as_ref() {
484 match callback(run_ctx.clone() as Arc<dyn CallbackContext>).await {
485 Ok(Some(content)) => {
486 let mut after_event = Event::new(run_ctx.invocation_id());
487 after_event.author = agent_name.clone();
488 after_event.llm_response.content = Some(content);
489 yield Ok(after_event);
490 break;
491 }
492 Ok(None) => continue,
493 Err(e) => { yield Err(e); return; }
494 }
495 }
496 };
497
498 Ok(Box::pin(s))
499 }
500}