1use adk_core::{
2 AfterAgentCallback, AfterModelCallback, AfterToolCallback, AfterToolCallbackFull, Agent,
3 BeforeAgentCallback, BeforeModelCallback, BeforeModelResult, BeforeToolCallback,
4 CallbackContext, Content, Event, EventActions, FunctionResponseData, GlobalInstructionProvider,
5 InstructionProvider, InvocationContext, Llm, LlmRequest, LlmResponse, MemoryEntry,
6 OnToolErrorCallback, Part, ReadonlyContext, Result, RetryBudget, Tool, ToolCallbackContext,
7 ToolConfirmationDecision, ToolConfirmationPolicy, ToolConfirmationRequest, ToolContext,
8 ToolExecutionStrategy, ToolOutcome, Toolset,
9};
10use async_stream::stream;
11use async_trait::async_trait;
12use std::{
13 collections::HashMap,
14 sync::{Arc, Mutex},
15};
16use tracing::Instrument;
17
18#[cfg(feature = "enhanced-plugins")]
19use adk_plugin::{
20 BeforeModelCallResult, BeforeToolCallResult, EnhancedPlugin, EnhancedPluginManager,
21};
22
23#[cfg(feature = "skills")]
24use crate::skill_shim::load_skill_index;
25use crate::{
26 guardrails::{
27 GuardrailSet, ToolGuardrailSet, ToolScreening, enforce_guardrails, screen_tool_call,
28 },
29 skill_shim::{SelectionPolicy, SkillIndex, apply_skill_injection},
30 tool_call_markup::normalize_option_content,
31 workflow::with_user_content_override,
32};
33
34pub const DEFAULT_MAX_ITERATIONS: u32 = 100;
36
37pub const DEFAULT_TOOL_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(300);
39
40fn trace_json_payload<T: serde::Serialize>(
41 value: &T,
42 record_payloads: bool,
43 max_bytes: usize,
44) -> String {
45 let json = serde_json::to_string(value).unwrap_or_default();
46 if cfg!(feature = "record-payloads") && record_payloads {
47 return json;
48 }
49
50 let max_bytes = max_bytes.max(32);
51 if json.len() <= max_bytes {
52 return json;
53 }
54
55 let mut end = max_bytes;
56 while !json.is_char_boundary(end) {
57 end -= 1;
58 }
59 format!("{}...[truncated {} bytes]", &json[..end], json.len() - end)
60}
61
62#[derive(Debug, Clone)]
63struct PendingToolCall {
64 index: usize,
65 name: String,
66 args: serde_json::Value,
67 id: Option<String>,
68 function_call_id: String,
69 guardrail_denial: Option<String>,
70}
71
72fn build_generation_config(
73 base: Option<&adk_core::GenerateContentConfig>,
74 output_schema: Option<&serde_json::Value>,
75 cached_content: Option<&str>,
76) -> Option<adk_core::GenerateContentConfig> {
77 let mut config = base.cloned().unwrap_or_default();
78 if let Some(schema) = output_schema {
79 config.response_schema = Some(schema.clone());
80 }
81 if config.cached_content.is_none()
82 && let Some(cached_content) = cached_content
83 {
84 config.cached_content = Some(cached_content.to_string());
85 }
86
87 if base.is_some() || output_schema.is_some() || cached_content.is_some() {
88 Some(config)
89 } else {
90 None
91 }
92}
93
94fn collect_function_calls(content: &Content, invocation_id: &str) -> Vec<PendingToolCall> {
95 content
96 .parts
97 .iter()
98 .filter_map(|part| {
99 if let Part::FunctionCall { name, args, id, .. } = part {
100 Some((name, args, id))
101 } else {
102 None
103 }
104 })
105 .enumerate()
106 .map(|(index, (name, args, id))| PendingToolCall {
107 index,
108 name: name.clone(),
109 args: args.clone(),
110 id: id.clone(),
111 function_call_id: id
112 .clone()
113 .unwrap_or_else(|| format!("{invocation_id}_{name}_{index}")),
114 guardrail_denial: None,
115 })
116 .collect()
117}
118
119fn build_partial_llm_event(
120 event_id: &str,
121 invocation_id: &str,
122 agent_name: &str,
123 request_json: &str,
124 chunk: &LlmResponse,
125 long_running_tool_ids: Vec<String>,
126) -> Event {
127 let mut event = Event::with_id(event_id, invocation_id);
128 event.author = agent_name.to_string();
129 event.llm_request = Some(request_json.to_string());
130 event
131 .provider_metadata
132 .insert("gcp.vertex.agent.llm_request".to_string(), request_json.to_string());
133 event.provider_metadata.insert(
134 "gcp.vertex.agent.llm_response".to_string(),
135 serde_json::to_string(chunk).unwrap_or_default(),
136 );
137 event.llm_response.partial = chunk.partial;
138 event.llm_response.turn_complete = chunk.turn_complete;
139 event.llm_response.finish_reason = chunk.finish_reason;
140 event.llm_response.usage_metadata = chunk.usage_metadata.clone();
141 event.llm_response.content = chunk.content.clone();
142 event.llm_response.provider_metadata = chunk.provider_metadata.clone();
143 event.llm_response.interaction_id = chunk.interaction_id.clone();
144 event.llm_response.interrupted = chunk.interrupted;
147 event.llm_response.error_code = chunk.error_code.clone();
148 event.llm_response.error_message = chunk.error_message.clone();
149 event.long_running_tool_ids = long_running_tool_ids;
150 event
151}
152
153fn build_final_llm_event(
154 event_id: &str,
155 invocation_id: &str,
156 agent_name: &str,
157 request_json: &str,
158 content: Option<&Content>,
159 last_chunk: Option<&LlmResponse>,
160 long_running_tool_ids: Vec<String>,
161) -> Event {
162 let mut event = Event::with_id(event_id, invocation_id);
163 event.author = agent_name.to_string();
164 event.llm_request = Some(request_json.to_string());
165 event
166 .provider_metadata
167 .insert("gcp.vertex.agent.llm_request".to_string(), request_json.to_string());
168 event.llm_response.content = content.cloned();
169 event.llm_response.partial = false;
170 event.llm_response.turn_complete = true;
171
172 if let Some(last_chunk) = last_chunk {
173 event.llm_response.finish_reason = last_chunk.finish_reason;
174 event.llm_response.usage_metadata = last_chunk.usage_metadata.clone();
175 event.llm_response.provider_metadata = last_chunk.provider_metadata.clone();
176 event.llm_response.interaction_id = last_chunk.interaction_id.clone();
177 event.llm_response.interrupted = last_chunk.interrupted;
178 event.llm_response.error_code = last_chunk.error_code.clone();
179 event.llm_response.error_message = last_chunk.error_message.clone();
180 event.provider_metadata.insert(
181 "gcp.vertex.agent.llm_response".to_string(),
182 serde_json::to_string(last_chunk).unwrap_or_default(),
183 );
184 }
185
186 event.long_running_tool_ids = long_running_tool_ids;
187 event
188}
189
190pub struct LlmAgent {
198 name: String,
199 description: String,
200 model: Arc<dyn Llm>,
201 instruction: Option<String>,
202 instruction_provider: Option<Arc<InstructionProvider>>,
203 global_instruction: Option<String>,
204 global_instruction_provider: Option<Arc<GlobalInstructionProvider>>,
205 skills_index: Option<Arc<SkillIndex>>,
206 skill_policy: SelectionPolicy,
207 max_skill_chars: usize,
208 #[allow(dead_code)] input_schema: Option<serde_json::Value>,
210 output_schema: Option<serde_json::Value>,
211 output_max_retries: usize,
213 disallow_transfer_to_parent: bool,
214 disallow_transfer_to_peers: bool,
215 include_contents: adk_core::IncludeContents,
216 tools: Vec<Arc<dyn Tool>>,
217 toolsets: Vec<Arc<dyn Toolset>>,
218 sub_agents: Vec<Arc<dyn Agent>>,
219 output_key: Option<String>,
220 generate_content_config: Option<adk_core::GenerateContentConfig>,
222 max_iterations: u32,
224 tool_timeout: std::time::Duration,
226 before_callbacks: Arc<Vec<BeforeAgentCallback>>,
227 after_callbacks: Arc<Vec<AfterAgentCallback>>,
228 before_model_callbacks: Arc<Vec<BeforeModelCallback>>,
229 after_model_callbacks: Arc<Vec<AfterModelCallback>>,
230 before_tool_callbacks: Arc<Vec<BeforeToolCallback>>,
231 after_tool_callbacks: Arc<Vec<AfterToolCallback>>,
232 on_tool_error_callbacks: Arc<Vec<OnToolErrorCallback>>,
233 after_tool_callbacks_full: Arc<Vec<AfterToolCallbackFull>>,
235 default_retry_budget: Option<RetryBudget>,
237 tool_retry_budgets: std::collections::HashMap<String, RetryBudget>,
239 circuit_breaker_threshold: Option<u32>,
242 tool_confirmation_policy: ToolConfirmationPolicy,
243 tool_execution_strategy: Option<ToolExecutionStrategy>,
246 input_guardrails: Arc<GuardrailSet>,
247 output_guardrails: Arc<GuardrailSet>,
248 tool_guardrails: Arc<ToolGuardrailSet>,
249 #[cfg(feature = "enhanced-plugins")]
252 enhanced_plugin_manager: Option<Arc<EnhancedPluginManager>>,
253 #[cfg(feature = "sandbox")]
257 sandbox_config: Option<adk_sandbox::workspace::SandboxConfig>,
258}
259
260struct PromptConfig {
261 instruction: Option<String>,
262 instruction_provider: Option<Arc<InstructionProvider>>,
263 global_instruction: Option<String>,
264 global_instruction_provider: Option<Arc<GlobalInstructionProvider>>,
265 skills_index: Option<Arc<SkillIndex>>,
266 skill_policy: SelectionPolicy,
267 max_skill_chars: usize,
268 output_schema: Option<serde_json::Value>,
269 include_contents: adk_core::IncludeContents,
270}
271
272impl PromptConfig {
273 fn from_agent(agent: &LlmAgent) -> Self {
274 Self {
275 instruction: agent.instruction.clone(),
276 instruction_provider: agent.instruction_provider.clone(),
277 global_instruction: agent.global_instruction.clone(),
278 global_instruction_provider: agent.global_instruction_provider.clone(),
279 skills_index: agent.skills_index.clone(),
280 skill_policy: agent.skill_policy.clone(),
281 max_skill_chars: agent.max_skill_chars,
282 output_schema: agent.output_schema.clone(),
283 include_contents: agent.include_contents,
284 }
285 }
286
287 async fn prepare_conversation(
288 &self,
289 ctx: &Arc<dyn InvocationContext>,
290 agent_name: &str,
291 ) -> Result<Vec<Content>> {
292 let mut preamble = Vec::new();
293
294 if let Some(provider) = &self.global_instruction_provider {
295 let instruction = provider(ctx.clone() as Arc<dyn ReadonlyContext>).await?;
296 if !instruction.is_empty() {
297 preamble.push(Content::new("user").with_text(instruction));
298 }
299 } else if let Some(template) = &self.global_instruction {
300 let instruction = adk_core::inject_session_state(ctx.as_ref(), template).await?;
301 if !instruction.is_empty() {
302 preamble.push(Content::new("user").with_text(instruction));
303 }
304 }
305
306 if let Some(provider) = &self.instruction_provider {
307 let instruction = provider(ctx.clone() as Arc<dyn ReadonlyContext>).await?;
308 if !instruction.is_empty() {
309 preamble.push(Content::new("user").with_text(instruction));
310 }
311 } else if let Some(template) = &self.instruction {
312 let instruction = adk_core::inject_session_state(ctx.as_ref(), template).await?;
313 if !instruction.is_empty() {
314 preamble.push(Content::new("user").with_text(instruction));
315 }
316 }
317
318 if let Some(schema) = &self.output_schema {
319 preamble.push(Content::new("user").with_text(format!(
320 "You MUST respond with valid JSON conforming to this schema: {schema}. Do not include any text outside the JSON object."
321 )));
322 }
323
324 let agent_filter = if ctx.authoritative_transfer_targets()
325 || !ctx.run_config().transfer_targets.is_empty()
326 {
327 Some(agent_name)
328 } else {
329 None
330 };
331 let mut session_history =
332 ctx.session().conversation_history_scoped(agent_filter, ctx.branch());
333 let mut current_user_content = ctx.user_content().clone();
334 if let Some(index) = &self.skills_index {
335 apply_skill_injection(
336 &mut current_user_content,
337 index.as_ref(),
338 &self.skill_policy,
339 self.max_skill_chars,
340 );
341 }
342 if let Some(index) = session_history.iter().rposition(|content| content.role == "user") {
343 session_history[index] = current_user_content.clone();
344 } else {
345 session_history.push(current_user_content.clone());
346 }
347
348 Ok(match self.include_contents {
349 adk_core::IncludeContents::None => {
350 preamble.push(current_user_content);
351 preamble
352 }
353 adk_core::IncludeContents::Default => {
354 preamble.extend(session_history);
355 preamble
356 }
357 })
358 }
359}
360
361struct ToolSetup {
362 tools: Vec<Arc<dyn Tool>>,
363 toolsets: Vec<Arc<dyn Toolset>>,
364 sub_agents: Vec<Arc<dyn Agent>>,
365 disallow_transfer_to_parent: bool,
366 disallow_transfer_to_peers: bool,
367}
368
369struct ResolvedTools {
370 map: HashMap<String, Arc<dyn Tool>>,
371 declarations: HashMap<String, serde_json::Value>,
372 transfer_targets: Vec<String>,
373}
374
375impl ToolSetup {
376 fn from_agent(agent: &LlmAgent) -> Self {
377 Self {
378 tools: agent.tools.clone(),
379 toolsets: agent.toolsets.clone(),
380 sub_agents: agent.sub_agents.clone(),
381 disallow_transfer_to_parent: agent.disallow_transfer_to_parent,
382 disallow_transfer_to_peers: agent.disallow_transfer_to_peers,
383 }
384 }
385
386 async fn resolve(&self, ctx: &Arc<dyn InvocationContext>) -> Result<ResolvedTools> {
387 let mut tools = self.tools.clone();
388 let static_tool_names: std::collections::HashSet<_> =
389 tools.iter().map(|tool| tool.name().to_string()).collect();
390 let mut toolset_sources = std::collections::HashMap::<String, String>::new();
391 let mut active_toolsets: Vec<&dyn Toolset> =
392 self.toolsets.iter().map(AsRef::as_ref).collect();
393 active_toolsets.extend(
394 ctx.run_config().runtime_toolsets.iter().map(|runtime| runtime.toolset().as_ref()),
395 );
396
397 for toolset in active_toolsets {
398 for tool in toolset.tools(ctx.clone() as Arc<dyn ReadonlyContext>).await? {
399 let name = tool.name().to_string();
400 if static_tool_names.contains(&name) {
401 return Err(adk_core::AdkError::agent(format!(
402 "Duplicate tool name '{name}': conflict between static tool and toolset '{}'",
403 toolset.name()
404 )));
405 }
406 if let Some(other_toolset) = toolset_sources.get(&name) {
407 return Err(adk_core::AdkError::agent(format!(
408 "Duplicate tool name '{name}': conflict between toolset '{other_toolset}' and toolset '{}'",
409 toolset.name()
410 )));
411 }
412 toolset_sources.insert(name, toolset.name().to_string());
413 tools.push(tool);
414 }
415 }
416
417 let map = tools.iter().map(|tool| (tool.name().to_string(), tool.clone())).collect();
418 let mut declarations = tools
419 .iter()
420 .map(|tool| (tool.name().to_string(), tool.declaration()))
421 .collect::<std::collections::HashMap<_, _>>();
422 let mut transfer_targets: Vec<String> = if ctx.authoritative_transfer_targets() {
423 Vec::new()
424 } else {
425 self.sub_agents.iter().map(|agent| agent.name().to_string()).collect()
426 };
427 let child_names: std::collections::HashSet<_> =
428 self.sub_agents.iter().map(|agent| agent.name()).collect();
429 let parent_name = ctx.run_config().parent_agent.as_deref();
430
431 for target in &ctx.run_config().transfer_targets {
432 if child_names.contains(target.as_str()) {
433 continue;
434 }
435 let is_parent = parent_name == Some(target.as_str());
436 if (is_parent && self.disallow_transfer_to_parent)
437 || (!is_parent && self.disallow_transfer_to_peers)
438 {
439 continue;
440 }
441 transfer_targets.push(target.clone());
442 }
443
444 if !transfer_targets.is_empty() {
445 declarations.insert(
446 "transfer_to_agent".to_string(),
447 serde_json::json!({
448 "name": "transfer_to_agent",
449 "description": format!(
450 "Transfer execution to another agent. Valid targets: {}",
451 transfer_targets.join(", ")
452 ),
453 "parameters": {
454 "type": "object",
455 "properties": {
456 "agent_name": {
457 "type": "string",
458 "description": "The name of the agent to transfer to.",
459 "enum": transfer_targets
460 }
461 },
462 "required": ["agent_name"]
463 }
464 }),
465 );
466 }
467
468 Ok(ResolvedTools { map, declarations, transfer_targets })
469 }
470}
471
472impl std::fmt::Debug for LlmAgent {
473 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
474 f.debug_struct("LlmAgent")
475 .field("name", &self.name)
476 .field("description", &self.description)
477 .field("model", &self.model.name())
478 .field("instruction", &self.instruction)
479 .field("tools_count", &self.tools.len())
480 .field("sub_agents_count", &self.sub_agents.len())
481 .finish()
482 }
483}
484
485fn static_confirmation_decision(
493 decisions: &std::collections::HashMap<String, ToolConfirmationDecision>,
494 fingerprints: &std::collections::HashMap<String, String>,
495 function_call_id: &str,
496 tool_name: &str,
497 args: &serde_json::Value,
498) -> Option<ToolConfirmationDecision> {
499 let decision = decisions.get(function_call_id).copied()?;
500 if let Some(expected) = fingerprints.get(function_call_id) {
501 let actual = adk_core::tool_call_fingerprint(tool_name, args);
502 if &actual != expected {
503 tracing::warn!(
504 tool.name = %tool_name,
505 function_call.id = %function_call_id,
506 "confirmation decision does not match this call's arguments, treating as unconfirmed"
507 );
508 return None;
509 }
510 }
511 Some(decision)
512}
513
514impl LlmAgent {
515 #[cfg(feature = "sandbox")]
522 pub fn sandbox_config(&self) -> Option<&adk_sandbox::workspace::SandboxConfig> {
523 self.sandbox_config.as_ref()
524 }
525
526 async fn apply_input_guardrails(
527 ctx: Arc<dyn InvocationContext>,
528 input_guardrails: Arc<GuardrailSet>,
529 ) -> Result<Arc<dyn InvocationContext>> {
530 let content =
531 enforce_guardrails(input_guardrails.as_ref(), ctx.user_content(), "input").await?;
532 if content.role != ctx.user_content().role || content.parts != ctx.user_content().parts {
533 Ok(with_user_content_override(ctx, content))
534 } else {
535 Ok(ctx)
536 }
537 }
538
539 async fn apply_output_guardrails(
540 output_guardrails: &GuardrailSet,
541 content: Content,
542 ) -> Result<Content> {
543 enforce_guardrails(output_guardrails, &content, "output").await
544 }
545
546 fn history_parts_from_provider_metadata(
547 provider_metadata: Option<&serde_json::Value>,
548 ) -> Vec<Part> {
549 let Some(provider_metadata) = provider_metadata else {
550 return Vec::new();
551 };
552
553 let history_parts = provider_metadata
554 .get("conversation_history_parts")
555 .or_else(|| {
556 provider_metadata
557 .get("openai")
558 .and_then(|openai| openai.get("conversation_history_parts"))
559 })
560 .and_then(serde_json::Value::as_array);
561
562 history_parts
563 .into_iter()
564 .flatten()
565 .filter_map(|value| serde_json::from_value::<Part>(value.clone()).ok())
566 .collect()
567 }
568
569 fn augment_content_for_history(
570 content: &Content,
571 provider_metadata: Option<&serde_json::Value>,
572 ) -> Content {
573 let mut augmented = content.clone();
574 augmented.parts.extend(Self::history_parts_from_provider_metadata(provider_metadata));
575 augmented
576 }
577}
578
579fn validate_output_against_schema(
584 text: &str,
585 schema: &serde_json::Value,
586) -> std::result::Result<serde_json::Value, String> {
587 let parsed: serde_json::Value =
588 serde_json::from_str(text).map_err(|e| format!("Response is not valid JSON: {e}"))?;
589
590 let validator =
591 jsonschema::validator_for(schema).map_err(|e| format!("Invalid schema: {e}"))?;
592
593 let errors: Vec<String> = validator.iter_errors(&parsed).map(|e| e.to_string()).collect();
594
595 if errors.is_empty() { Ok(parsed) } else { Err(errors.join("; ")) }
596}
597
598fn extract_text_from_events(events: &[Event]) -> Option<String> {
603 for event in events.iter().rev() {
604 if let Some(ref content) = event.llm_response.content {
605 let text: String =
606 content
607 .parts
608 .iter()
609 .filter_map(|p| {
610 if let Part::Text { text } = p { Some(text.as_str()) } else { None }
611 })
612 .collect::<Vec<_>>()
613 .join("");
614 if !text.is_empty() {
615 return Some(text);
616 }
617 }
618 }
619 None
620}
621
622pub fn extract_typed<T: serde::de::DeserializeOwned>(events: &[Event]) -> Result<T> {
644 let text = extract_text_from_events(events).ok_or_else(|| {
645 adk_core::AdkError::agent("no text content found in events for typed extraction")
646 })?;
647
648 serde_json::from_str(&text)
649 .map_err(|e| adk_core::AdkError::agent(format!("output deserialization failed: {e}")))
650}
651
652pub struct LlmAgentBuilder {
654 name: String,
655 description: Option<String>,
656 model: Option<Arc<dyn Llm>>,
657 instruction: Option<String>,
658 instruction_provider: Option<Arc<InstructionProvider>>,
659 global_instruction: Option<String>,
660 global_instruction_provider: Option<Arc<GlobalInstructionProvider>>,
661 skills_index: Option<Arc<SkillIndex>>,
662 skill_policy: SelectionPolicy,
663 max_skill_chars: usize,
664 input_schema: Option<serde_json::Value>,
665 output_schema: Option<serde_json::Value>,
666 output_max_retries: usize,
667 disallow_transfer_to_parent: bool,
668 disallow_transfer_to_peers: bool,
669 include_contents: adk_core::IncludeContents,
670 tools: Vec<Arc<dyn Tool>>,
671 toolsets: Vec<Arc<dyn Toolset>>,
672 sub_agents: Vec<Arc<dyn Agent>>,
673 output_key: Option<String>,
674 generate_content_config: Option<adk_core::GenerateContentConfig>,
675 max_iterations: u32,
676 tool_timeout: std::time::Duration,
677 before_callbacks: Vec<BeforeAgentCallback>,
678 after_callbacks: Vec<AfterAgentCallback>,
679 before_model_callbacks: Vec<BeforeModelCallback>,
680 after_model_callbacks: Vec<AfterModelCallback>,
681 before_tool_callbacks: Vec<BeforeToolCallback>,
682 after_tool_callbacks: Vec<AfterToolCallback>,
683 on_tool_error_callbacks: Vec<OnToolErrorCallback>,
684 after_tool_callbacks_full: Vec<AfterToolCallbackFull>,
685 default_retry_budget: Option<RetryBudget>,
686 tool_retry_budgets: std::collections::HashMap<String, RetryBudget>,
687 circuit_breaker_threshold: Option<u32>,
688 tool_confirmation_policy: ToolConfirmationPolicy,
689 tool_execution_strategy: Option<ToolExecutionStrategy>,
690 input_guardrails: GuardrailSet,
691 output_guardrails: GuardrailSet,
692 tool_guardrails: ToolGuardrailSet,
693 #[cfg(feature = "enhanced-plugins")]
695 enhanced_plugins: Vec<Arc<dyn EnhancedPlugin>>,
696 #[cfg(feature = "sandbox")]
698 sandbox_config: Option<adk_sandbox::workspace::SandboxConfig>,
699}
700
701impl LlmAgentBuilder {
702 pub fn new(name: impl Into<String>) -> Self {
704 Self {
705 name: name.into(),
706 description: None,
707 model: None,
708 instruction: None,
709 instruction_provider: None,
710 global_instruction: None,
711 global_instruction_provider: None,
712 skills_index: None,
713 skill_policy: SelectionPolicy::default(),
714 max_skill_chars: 2000,
715 input_schema: None,
716 output_schema: None,
717 output_max_retries: 3,
718 disallow_transfer_to_parent: false,
719 disallow_transfer_to_peers: false,
720 include_contents: adk_core::IncludeContents::Default,
721 tools: Vec::new(),
722 toolsets: Vec::new(),
723 sub_agents: Vec::new(),
724 output_key: None,
725 generate_content_config: None,
726 max_iterations: DEFAULT_MAX_ITERATIONS,
727 tool_timeout: DEFAULT_TOOL_TIMEOUT,
728 before_callbacks: Vec::new(),
729 after_callbacks: Vec::new(),
730 before_model_callbacks: Vec::new(),
731 after_model_callbacks: Vec::new(),
732 before_tool_callbacks: Vec::new(),
733 after_tool_callbacks: Vec::new(),
734 on_tool_error_callbacks: Vec::new(),
735 after_tool_callbacks_full: Vec::new(),
736 default_retry_budget: None,
737 tool_retry_budgets: std::collections::HashMap::new(),
738 circuit_breaker_threshold: None,
739 tool_confirmation_policy: ToolConfirmationPolicy::Never,
740 tool_execution_strategy: None,
741 input_guardrails: GuardrailSet::new(),
742 output_guardrails: GuardrailSet::new(),
743 tool_guardrails: ToolGuardrailSet::new(),
744 #[cfg(feature = "enhanced-plugins")]
745 enhanced_plugins: Vec::new(),
746 #[cfg(feature = "sandbox")]
747 sandbox_config: None,
748 }
749 }
750
751 pub fn description(mut self, desc: impl Into<String>) -> Self {
753 self.description = Some(desc.into());
754 self
755 }
756
757 pub fn model(mut self, model: Arc<dyn Llm>) -> Self {
759 self.model = Some(model);
760 self
761 }
762
763 pub fn instruction(mut self, instruction: impl Into<String>) -> Self {
765 self.instruction = Some(instruction.into());
766 self
767 }
768
769 pub fn instruction_provider(mut self, provider: InstructionProvider) -> Self {
771 self.instruction_provider = Some(Arc::new(provider));
772 self
773 }
774
775 pub fn global_instruction(mut self, instruction: impl Into<String>) -> Self {
777 self.global_instruction = Some(instruction.into());
778 self
779 }
780
781 pub fn global_instruction_provider(mut self, provider: GlobalInstructionProvider) -> Self {
783 self.global_instruction_provider = Some(Arc::new(provider));
784 self
785 }
786
787 #[cfg(feature = "skills")]
792 pub fn with_skills(mut self, index: SkillIndex) -> Self {
793 self.skills_index = Some(Arc::new(index));
794 self
795 }
796
797 #[cfg(feature = "skills")]
799 pub fn with_auto_skills(self) -> Result<Self> {
800 self.with_skills_from_root(".")
801 }
802
803 #[cfg(feature = "skills")]
805 pub fn with_skills_from_root(mut self, root: impl AsRef<std::path::Path>) -> Result<Self> {
806 let index = load_skill_index(root).map_err(|e| adk_core::AdkError::agent(e.to_string()))?;
807 self.skills_index = Some(Arc::new(index));
808 Ok(self)
809 }
810
811 #[cfg(feature = "skills")]
813 pub fn with_skill_policy(mut self, policy: SelectionPolicy) -> Self {
814 self.skill_policy = policy;
815 self
816 }
817
818 #[cfg(feature = "skills")]
820 pub fn with_skill_budget(mut self, max_chars: usize) -> Self {
821 self.max_skill_chars = max_chars;
822 self
823 }
824
825 pub fn input_schema(mut self, schema: serde_json::Value) -> Self {
827 self.input_schema = Some(schema);
828 self
829 }
830
831 pub fn output_schema(mut self, schema: serde_json::Value) -> Self {
833 self.output_schema = Some(schema);
834 self
835 }
836
837 pub fn output_type<T: schemars::JsonSchema>(mut self) -> Self {
860 let schema = schemars::schema_for!(T);
861 self.output_schema =
862 Some(serde_json::to_value(schema).expect("schema serialization cannot fail"));
863 self
864 }
865
866 pub fn output_max_retries(mut self, n: usize) -> Self {
871 self.output_max_retries = n;
872 self
873 }
874
875 pub fn disallow_transfer_to_parent(mut self, disallow: bool) -> Self {
877 self.disallow_transfer_to_parent = disallow;
878 self
879 }
880
881 pub fn disallow_transfer_to_peers(mut self, disallow: bool) -> Self {
883 self.disallow_transfer_to_peers = disallow;
884 self
885 }
886
887 pub fn include_contents(mut self, include: adk_core::IncludeContents) -> Self {
889 self.include_contents = include;
890 self
891 }
892
893 pub fn output_key(mut self, key: impl Into<String>) -> Self {
895 self.output_key = Some(key.into());
896 self
897 }
898
899 pub fn generate_content_config(mut self, config: adk_core::GenerateContentConfig) -> Self {
920 self.generate_content_config = Some(config);
921 self
922 }
923
924 pub fn temperature(mut self, temperature: f32) -> Self {
927 self.generate_content_config
928 .get_or_insert(adk_core::GenerateContentConfig::default())
929 .temperature = Some(temperature);
930 self
931 }
932
933 pub fn top_p(mut self, top_p: f32) -> Self {
935 self.generate_content_config
936 .get_or_insert(adk_core::GenerateContentConfig::default())
937 .top_p = Some(top_p);
938 self
939 }
940
941 pub fn top_k(mut self, top_k: i32) -> Self {
943 self.generate_content_config
944 .get_or_insert(adk_core::GenerateContentConfig::default())
945 .top_k = Some(top_k);
946 self
947 }
948
949 pub fn max_output_tokens(mut self, max_tokens: i32) -> Self {
951 self.generate_content_config
952 .get_or_insert(adk_core::GenerateContentConfig::default())
953 .max_output_tokens = Some(max_tokens);
954 self
955 }
956
957 pub fn max_iterations(mut self, max: u32) -> Self {
960 self.max_iterations = max;
961 self
962 }
963
964 pub fn tool_timeout(mut self, timeout: std::time::Duration) -> Self {
967 self.tool_timeout = timeout;
968 self
969 }
970
971 pub fn tool(mut self, tool: Arc<dyn Tool>) -> Self {
973 self.tools.push(tool);
974 self
975 }
976
977 pub fn toolset(mut self, toolset: Arc<dyn Toolset>) -> Self {
983 self.toolsets.push(toolset);
984 self
985 }
986
987 pub fn sub_agent(mut self, agent: Arc<dyn Agent>) -> Self {
989 self.sub_agents.push(agent);
990 self
991 }
992
993 pub fn before_callback(mut self, callback: BeforeAgentCallback) -> Self {
995 self.before_callbacks.push(callback);
996 self
997 }
998
999 pub fn after_callback(mut self, callback: AfterAgentCallback) -> Self {
1001 self.after_callbacks.push(callback);
1002 self
1003 }
1004
1005 pub fn before_model_callback(mut self, callback: BeforeModelCallback) -> Self {
1007 self.before_model_callbacks.push(callback);
1008 self
1009 }
1010
1011 pub fn after_model_callback(mut self, callback: AfterModelCallback) -> Self {
1013 self.after_model_callbacks.push(callback);
1014 self
1015 }
1016
1017 pub fn before_tool_callback(mut self, callback: BeforeToolCallback) -> Self {
1019 self.before_tool_callbacks.push(callback);
1020 self
1021 }
1022
1023 pub fn after_tool_callback(mut self, callback: AfterToolCallback) -> Self {
1025 self.after_tool_callbacks.push(callback);
1026 self
1027 }
1028
1029 pub fn after_tool_callback_full(mut self, callback: AfterToolCallbackFull) -> Self {
1044 self.after_tool_callbacks_full.push(callback);
1045 self
1046 }
1047
1048 pub fn on_tool_error(mut self, callback: OnToolErrorCallback) -> Self {
1056 self.on_tool_error_callbacks.push(callback);
1057 self
1058 }
1059
1060 pub fn default_retry_budget(mut self, budget: RetryBudget) -> Self {
1067 self.default_retry_budget = Some(budget);
1068 self
1069 }
1070
1071 pub fn tool_retry_budget(mut self, tool_name: impl Into<String>, budget: RetryBudget) -> Self {
1076 self.tool_retry_budgets.insert(tool_name.into(), budget);
1077 self
1078 }
1079
1080 pub fn circuit_breaker_threshold(mut self, threshold: u32) -> Self {
1087 self.circuit_breaker_threshold = Some(threshold);
1088 self
1089 }
1090
1091 pub fn tool_confirmation_policy(mut self, policy: ToolConfirmationPolicy) -> Self {
1093 self.tool_confirmation_policy = policy;
1094 self
1095 }
1096
1097 pub fn require_tool_confirmation(mut self, tool_name: impl Into<String>) -> Self {
1099 self.tool_confirmation_policy = self.tool_confirmation_policy.with_tool(tool_name);
1100 self
1101 }
1102
1103 pub fn require_tool_confirmation_for_all(mut self) -> Self {
1105 self.tool_confirmation_policy = ToolConfirmationPolicy::Always;
1106 self
1107 }
1108
1109 pub fn tool_execution_strategy(mut self, strategy: ToolExecutionStrategy) -> Self {
1117 self.tool_execution_strategy = Some(strategy);
1118 self
1119 }
1120
1121 pub fn input_guardrails(mut self, guardrails: GuardrailSet) -> Self {
1130 self.input_guardrails = guardrails;
1131 self
1132 }
1133
1134 pub fn output_guardrails(mut self, guardrails: GuardrailSet) -> Self {
1143 self.output_guardrails = guardrails;
1144 self
1145 }
1146
1147 pub fn tool_guardrails(mut self, guardrails: ToolGuardrailSet) -> Self {
1173 self.tool_guardrails = guardrails;
1174 self
1175 }
1176
1177 #[cfg(feature = "enhanced-plugins")]
1197 pub fn enhanced_plugin(mut self, plugin: Arc<dyn EnhancedPlugin>) -> Self {
1198 self.enhanced_plugins.push(plugin);
1199 self
1200 }
1201
1202 #[cfg(feature = "enhanced-plugins")]
1224 pub fn enhanced_plugins(mut self, plugins: Vec<Arc<dyn EnhancedPlugin>>) -> Self {
1225 self.enhanced_plugins.extend(plugins);
1226 self
1227 }
1228
1229 #[cfg(feature = "sandbox")]
1264 pub fn sandbox_config(mut self, config: adk_sandbox::workspace::SandboxConfig) -> Self {
1265 self.sandbox_config = Some(config);
1266 self
1267 }
1268
1269 pub fn build(self) -> Result<LlmAgent> {
1271 let model = self.model.ok_or_else(|| adk_core::AdkError::agent("Model is required"))?;
1272
1273 let mut seen_names = std::collections::HashSet::new();
1274 for agent in &self.sub_agents {
1275 if !seen_names.insert(agent.name()) {
1276 return Err(adk_core::AdkError::agent(format!(
1277 "Duplicate sub-agent name: {}",
1278 agent.name()
1279 )));
1280 }
1281 }
1282
1283 #[cfg(feature = "sandbox")]
1286 if let Some(ref sandbox_cfg) = self.sandbox_config {
1287 use adk_sandbox::workspace::Capability;
1288 if model.uses_interactions_api()
1289 && (sandbox_cfg.capabilities.contains(&Capability::Shell)
1290 || sandbox_cfg.capabilities.contains(&Capability::Filesystem))
1291 {
1292 return Err(adk_core::AdkError::new(
1293 adk_core::ErrorComponent::Agent,
1294 adk_core::ErrorCategory::InvalidInput,
1295 "code.gemini_interactions_conflict",
1296 "Cannot combine Gemini Interactions API (server-managed environment) \
1297 with client-side sandbox tools (Shell/Filesystem). These provide \
1298 competing filesystems and would produce nondeterministic behavior. \
1299 Either disable use_interactions_api or remove sandbox capabilities.",
1300 ));
1301 }
1302 }
1303
1304 #[cfg(feature = "enhanced-plugins")]
1306 let enhanced_plugin_manager = if self.enhanced_plugins.is_empty() {
1307 None
1308 } else {
1309 Some(Arc::new(EnhancedPluginManager::new(self.enhanced_plugins)))
1310 };
1311
1312 Ok(LlmAgent {
1313 name: self.name,
1314 description: self.description.unwrap_or_default(),
1315 model,
1316 instruction: self.instruction,
1317 instruction_provider: self.instruction_provider,
1318 global_instruction: self.global_instruction,
1319 global_instruction_provider: self.global_instruction_provider,
1320 skills_index: self.skills_index,
1321 skill_policy: self.skill_policy,
1322 max_skill_chars: self.max_skill_chars,
1323 input_schema: self.input_schema,
1324 output_schema: self.output_schema,
1325 output_max_retries: self.output_max_retries,
1326 disallow_transfer_to_parent: self.disallow_transfer_to_parent,
1327 disallow_transfer_to_peers: self.disallow_transfer_to_peers,
1328 include_contents: self.include_contents,
1329 tools: self.tools,
1330 toolsets: self.toolsets,
1331 sub_agents: self.sub_agents,
1332 output_key: self.output_key,
1333 generate_content_config: self.generate_content_config,
1334 max_iterations: self.max_iterations,
1335 tool_timeout: self.tool_timeout,
1336 before_callbacks: Arc::new(self.before_callbacks),
1337 after_callbacks: Arc::new(self.after_callbacks),
1338 before_model_callbacks: Arc::new(self.before_model_callbacks),
1339 after_model_callbacks: Arc::new(self.after_model_callbacks),
1340 before_tool_callbacks: Arc::new(self.before_tool_callbacks),
1341 after_tool_callbacks: Arc::new(self.after_tool_callbacks),
1342 on_tool_error_callbacks: Arc::new(self.on_tool_error_callbacks),
1343 after_tool_callbacks_full: Arc::new(self.after_tool_callbacks_full),
1344 default_retry_budget: self.default_retry_budget,
1345 tool_retry_budgets: self.tool_retry_budgets,
1346 circuit_breaker_threshold: self.circuit_breaker_threshold,
1347 tool_confirmation_policy: self.tool_confirmation_policy,
1348 tool_execution_strategy: self.tool_execution_strategy,
1349 input_guardrails: Arc::new(self.input_guardrails),
1350 output_guardrails: Arc::new(self.output_guardrails),
1351 tool_guardrails: Arc::new(self.tool_guardrails),
1352 #[cfg(feature = "enhanced-plugins")]
1353 enhanced_plugin_manager,
1354 #[cfg(feature = "sandbox")]
1355 sandbox_config: self.sandbox_config,
1356 })
1357 }
1358}
1359
1360const TOOL_PROGRESS_CAPACITY: usize = 256;
1368
1369const TOOL_PROGRESS_SEND_TIMEOUT: std::time::Duration = std::time::Duration::from_millis(100);
1374
1375const TOOL_PROGRESS_MAX_CHUNK_BYTES: usize = 8 * 1024;
1377
1378const TOOL_PROGRESS_MAX_TOTAL_BYTES: usize = 1024 * 1024;
1380
1381const TOOL_PROGRESS_TRUNCATION_MARKER: &str = "[adk: tool progress truncated]";
1383
1384fn truncate_on_char_boundary(text: &str, max_bytes: usize) -> &str {
1386 if text.len() <= max_bytes {
1387 return text;
1388 }
1389 let mut end = max_bytes;
1390 while end > 0 && !text.is_char_boundary(end) {
1391 end -= 1;
1392 }
1393 &text[..end]
1394}
1395
1396struct AgentToolContext {
1397 parent_ctx: Arc<dyn InvocationContext>,
1398 function_call_id: String,
1399 tool_name: Option<String>,
1402 actions: Mutex<EventActions>,
1403 progress_tx: Option<tokio::sync::mpsc::Sender<Event>>,
1404 progress_bytes: std::sync::atomic::AtomicUsize,
1406 progress_truncated: std::sync::atomic::AtomicBool,
1408}
1409
1410impl AgentToolContext {
1411 fn new(parent_ctx: Arc<dyn InvocationContext>, function_call_id: String) -> Self {
1412 Self {
1413 parent_ctx,
1414 function_call_id,
1415 tool_name: None,
1416 actions: Mutex::new(EventActions::default()),
1417 progress_tx: None,
1418 progress_bytes: std::sync::atomic::AtomicUsize::new(0),
1419 progress_truncated: std::sync::atomic::AtomicBool::new(false),
1420 }
1421 }
1422
1423 fn with_tool_name(mut self, tool_name: impl Into<String>) -> Self {
1425 self.tool_name = Some(tool_name.into());
1426 self
1427 }
1428
1429 fn with_progress(mut self, tx: tokio::sync::mpsc::Sender<Event>) -> Self {
1432 self.progress_tx = Some(tx);
1433 self
1434 }
1435
1436 async fn request_secret(&self, name: &str, purpose: Option<&str>) -> Result<Option<String>> {
1441 let mut request = adk_core::SecretRequest::new(name)
1442 .with_identity(
1443 self.parent_ctx.app_name(),
1444 self.parent_ctx.user_id(),
1445 self.parent_ctx.session_id(),
1446 )
1447 .with_invocation_id(self.parent_ctx.invocation_id());
1448 if let Some(tool_name) = &self.tool_name {
1449 request = request.with_tool_name(tool_name);
1450 }
1451 if let Some(purpose) = purpose {
1452 request = request.with_purpose(purpose);
1453 }
1454 self.parent_ctx.get_secret_for(&request).await
1455 }
1456
1457 async fn forward_progress(
1464 &self,
1465 tx: &tokio::sync::mpsc::Sender<Event>,
1466 stream: &str,
1467 chunk: &str,
1468 ) {
1469 use std::sync::atomic::Ordering;
1470
1471 if self.progress_truncated.load(Ordering::Relaxed) {
1472 return;
1473 }
1474
1475 let payload = truncate_on_char_boundary(chunk, TOOL_PROGRESS_MAX_CHUNK_BYTES);
1478 let forwarded = self.progress_bytes.fetch_add(payload.len(), Ordering::Relaxed);
1479 if forwarded.saturating_add(payload.len()) > TOOL_PROGRESS_MAX_TOTAL_BYTES {
1480 self.mark_progress_truncated(tx, stream).await;
1481 return;
1482 }
1483
1484 let event = Event::tool_progress(
1485 self.parent_ctx.invocation_id(),
1486 self.parent_ctx.agent_name(),
1487 &self.function_call_id,
1488 stream,
1489 payload,
1490 );
1491
1492 match tx.try_send(event) {
1493 Ok(()) => {}
1494 Err(tokio::sync::mpsc::error::TrySendError::Full(event)) => {
1495 match tokio::time::timeout(TOOL_PROGRESS_SEND_TIMEOUT, tx.send(event)).await {
1498 Ok(Ok(())) => {}
1499 Ok(Err(_)) => {}
1500 Err(_) => self.mark_progress_truncated(tx, stream).await,
1501 }
1502 }
1503 Err(tokio::sync::mpsc::error::TrySendError::Closed(_)) => {}
1504 }
1505 }
1506
1507 async fn mark_progress_truncated(&self, tx: &tokio::sync::mpsc::Sender<Event>, stream: &str) {
1509 use std::sync::atomic::Ordering;
1510 if self.progress_truncated.swap(true, Ordering::Relaxed) {
1511 return;
1512 }
1513 tracing::warn!(
1514 function_call.id = %self.function_call_id,
1515 progress.stream = %stream,
1516 "tool progress exceeded its budget, remaining output is not forwarded"
1517 );
1518 let marker = Event::tool_progress(
1519 self.parent_ctx.invocation_id(),
1520 self.parent_ctx.agent_name(),
1521 &self.function_call_id,
1522 stream,
1523 TOOL_PROGRESS_TRUNCATION_MARKER,
1524 );
1525 let _ = tx.try_send(marker);
1526 }
1527
1528 fn actions_guard(&self) -> std::sync::MutexGuard<'_, EventActions> {
1529 self.actions.lock().unwrap_or_else(|e| e.into_inner())
1530 }
1531}
1532
1533#[async_trait]
1534impl ReadonlyContext for AgentToolContext {
1535 fn invocation_id(&self) -> &str {
1536 self.parent_ctx.invocation_id()
1537 }
1538
1539 fn agent_name(&self) -> &str {
1540 self.parent_ctx.agent_name()
1541 }
1542
1543 fn user_id(&self) -> &str {
1544 self.parent_ctx.user_id()
1546 }
1547
1548 fn app_name(&self) -> &str {
1549 self.parent_ctx.app_name()
1551 }
1552
1553 fn session_id(&self) -> &str {
1554 self.parent_ctx.session_id()
1556 }
1557
1558 fn branch(&self) -> &str {
1559 self.parent_ctx.branch()
1560 }
1561
1562 fn user_content(&self) -> &Content {
1563 self.parent_ctx.user_content()
1564 }
1565}
1566
1567#[async_trait]
1568impl CallbackContext for AgentToolContext {
1569 fn artifacts(&self) -> Option<Arc<dyn adk_core::Artifacts>> {
1570 self.parent_ctx.artifacts()
1572 }
1573
1574 fn shared_state(&self) -> Option<Arc<adk_core::SharedState>> {
1575 self.parent_ctx.shared_state()
1576 }
1577}
1578
1579#[async_trait]
1580impl ToolContext for AgentToolContext {
1581 fn function_call_id(&self) -> &str {
1582 &self.function_call_id
1583 }
1584
1585 fn actions(&self) -> EventActions {
1586 self.actions_guard().clone()
1587 }
1588
1589 fn set_actions(&self, actions: EventActions) {
1590 *self.actions_guard() = actions;
1591 }
1592
1593 async fn search_memory(&self, query: &str) -> Result<Vec<MemoryEntry>> {
1594 if let Some(memory) = self.parent_ctx.memory() {
1596 memory.search(query).await
1597 } else {
1598 Ok(vec![])
1599 }
1600 }
1601
1602 fn memory(&self) -> Option<Arc<dyn adk_core::Memory>> {
1603 self.parent_ctx.memory()
1604 }
1605
1606 fn session(&self) -> Option<&dyn adk_core::Session> {
1607 Some(self.parent_ctx.session())
1608 }
1609
1610 fn run_config(&self) -> Option<&adk_core::RunConfig> {
1611 Some(self.parent_ctx.run_config())
1612 }
1613
1614 fn is_cancelled(&self) -> bool {
1615 self.parent_ctx.is_cancelled()
1616 }
1617
1618 fn request_metadata(&self) -> HashMap<String, serde_json::Value> {
1619 self.parent_ctx.request_metadata()
1620 }
1621
1622 fn delegation_depth(&self) -> u32 {
1623 self.parent_ctx.delegation_depth()
1624 }
1625
1626 fn max_delegation_depth(&self) -> Option<u32> {
1627 self.parent_ctx.max_delegation_depth()
1628 }
1629
1630 fn orchestration_root_invocation_id(&self) -> &str {
1631 self.parent_ctx.orchestration_root_invocation_id()
1632 }
1633
1634 fn orchestration_edge_id(&self) -> Option<&str> {
1635 self.parent_ctx.orchestration_edge_id()
1636 }
1637
1638 async fn emit_event(&self, event: Event) {
1639 if let Some(tx) = &self.progress_tx
1640 && !tx.is_closed()
1641 {
1642 let _ = tokio::time::timeout(TOOL_PROGRESS_SEND_TIMEOUT, tx.send(event)).await;
1643 }
1644 }
1645
1646 fn user_scopes(&self) -> Vec<String> {
1647 self.parent_ctx.user_scopes()
1648 }
1649
1650 async fn get_secret(&self, name: &str) -> Result<Option<String>> {
1651 self.request_secret(name, None).await
1652 }
1653
1654 async fn get_secret_for_purpose(&self, name: &str, purpose: &str) -> Result<Option<String>> {
1655 self.request_secret(name, Some(purpose)).await
1656 }
1657
1658 async fn emit_progress(&self, stream: &str, chunk: &str) {
1659 if let Some(tx) = &self.progress_tx {
1662 if !tx.is_closed() {
1664 self.forward_progress(tx, stream, chunk).await;
1665 }
1666 }
1667 tracing::debug!(
1669 target: "adk_agent::tool_progress",
1670 tool_call_id = %self.function_call_id,
1671 stream = %stream,
1672 "{chunk}",
1673 );
1674 }
1675}
1676
1677struct ToolOutcomeCallbackContext {
1681 inner: Arc<dyn CallbackContext>,
1682 outcome: ToolOutcome,
1683}
1684
1685#[async_trait]
1686impl ReadonlyContext for ToolOutcomeCallbackContext {
1687 fn invocation_id(&self) -> &str {
1688 self.inner.invocation_id()
1689 }
1690
1691 fn agent_name(&self) -> &str {
1692 self.inner.agent_name()
1693 }
1694
1695 fn user_id(&self) -> &str {
1696 self.inner.user_id()
1697 }
1698
1699 fn app_name(&self) -> &str {
1700 self.inner.app_name()
1701 }
1702
1703 fn session_id(&self) -> &str {
1704 self.inner.session_id()
1705 }
1706
1707 fn branch(&self) -> &str {
1708 self.inner.branch()
1709 }
1710
1711 fn user_content(&self) -> &Content {
1712 self.inner.user_content()
1713 }
1714}
1715
1716#[async_trait]
1717impl CallbackContext for ToolOutcomeCallbackContext {
1718 fn artifacts(&self) -> Option<Arc<dyn adk_core::Artifacts>> {
1719 self.inner.artifacts()
1720 }
1721
1722 fn tool_outcome(&self) -> Option<ToolOutcome> {
1723 Some(self.outcome.clone())
1724 }
1725}
1726
1727struct CircuitBreakerState {
1737 threshold: u32,
1738 failures: std::collections::HashMap<String, u32>,
1740}
1741
1742impl CircuitBreakerState {
1743 fn new(threshold: u32) -> Self {
1744 Self { threshold, failures: std::collections::HashMap::new() }
1745 }
1746
1747 fn is_open(&self, tool_name: &str) -> bool {
1749 self.failures.get(tool_name).copied().unwrap_or(0) >= self.threshold
1750 }
1751
1752 fn record(&mut self, outcome: &ToolOutcome) {
1754 if outcome.success {
1755 self.failures.remove(&outcome.tool_name);
1756 } else {
1757 let count = self.failures.entry(outcome.tool_name.clone()).or_insert(0);
1758 *count += 1;
1759 }
1760 }
1761}
1762
1763struct ToolExecutionResult {
1764 index: usize,
1765 content: Content,
1766 actions: EventActions,
1767 escalate_or_skip: bool,
1768}
1769
1770struct ToolExecutor<'a> {
1771 ctx: Arc<dyn InvocationContext>,
1772 tool_map: &'a std::collections::HashMap<String, Arc<dyn Tool>>,
1773 tool_retry_budgets: &'a std::collections::HashMap<String, RetryBudget>,
1774 default_retry_budget: &'a Option<RetryBudget>,
1775 before_tool_callbacks: &'a Arc<Vec<BeforeToolCallback>>,
1776 after_tool_callbacks: &'a Arc<Vec<AfterToolCallback>>,
1777 after_tool_callbacks_full: &'a Arc<Vec<AfterToolCallbackFull>>,
1778 on_tool_error_callbacks: &'a Arc<Vec<OnToolErrorCallback>>,
1779 tool_confirmation_policy: &'a ToolConfirmationPolicy,
1780 cb_mutex: &'a std::sync::Mutex<Option<CircuitBreakerState>>,
1781 invocation_id: &'a str,
1782 concurrency_manager: &'a adk_core::ToolConcurrencyManager,
1783 progress_tx: tokio::sync::mpsc::Sender<Event>,
1784 tool_timeout: std::time::Duration,
1785 confirmation_decisions: &'a std::collections::HashMap<String, ToolConfirmationDecision>,
1786 confirmation_fingerprints: &'a std::collections::HashMap<String, String>,
1787 live_confirmation_decisions: &'a std::collections::HashMap<String, ToolConfirmationDecision>,
1788 #[cfg(feature = "enhanced-plugins")]
1789 enhanced_plugin_manager: &'a Option<Arc<EnhancedPluginManager>>,
1790}
1791
1792impl ToolExecutor<'_> {
1793 async fn execute(&self, call: PendingToolCall) -> ToolExecutionResult {
1794 let PendingToolCall { index, name, args, id, function_call_id, guardrail_denial } = call;
1795 let mut tool_actions = EventActions::default();
1796 let mut response_content: Option<Content> = None;
1797 let mut run_after_tool_callbacks = true;
1798 let mut tool_outcome_for_callback: Option<ToolOutcome> = None;
1799 let mut executed_tool: Option<Arc<dyn Tool>> = None;
1800 let mut executed_tool_response: Option<serde_json::Value> = None;
1801
1802 if let Some(reason) = guardrail_denial {
1803 let denied_content = Content {
1806 role: "function".to_string(),
1807 parts: vec![Part::FunctionResponse {
1808 function_response: FunctionResponseData::new(
1809 name.clone(),
1810 serde_json::json!({ "error": reason }),
1811 ),
1812 id: id.clone(),
1813 annotations: None,
1814 }],
1815 };
1816 return ToolExecutionResult {
1817 index,
1818 content: denied_content,
1819 actions: tool_actions,
1820 escalate_or_skip: false,
1821 };
1822 }
1823
1824 let _concurrency_permit = match self.concurrency_manager.acquire(&name).await {
1828 Ok(permit) => Some(permit),
1829 Err(e) => {
1830 let error_content = Content {
1832 role: "function".to_string(),
1833 parts: vec![Part::FunctionResponse {
1834 function_response: FunctionResponseData::new(
1835 name.clone(),
1836 serde_json::json!({ "error": e.to_string() }),
1837 ),
1838 id: id.clone(),
1839 annotations: None,
1840 }],
1841 };
1842 return ToolExecutionResult {
1843 index,
1844 content: error_content,
1845 actions: tool_actions,
1846 escalate_or_skip: false,
1847 };
1848 }
1849 };
1850
1851 if self.tool_confirmation_policy.requires_confirmation(&name) {
1853 match self.live_confirmation_decisions.get(&function_call_id).copied().or_else(|| {
1854 static_confirmation_decision(
1855 self.confirmation_decisions,
1856 self.confirmation_fingerprints,
1857 &function_call_id,
1858 &name,
1859 &args,
1860 )
1861 }) {
1862 Some(ToolConfirmationDecision::Approve) => {
1863 tool_actions.tool_confirmation_decision =
1864 Some(ToolConfirmationDecision::Approve);
1865 }
1866 Some(ToolConfirmationDecision::Deny) => {
1867 tool_actions.tool_confirmation_decision = Some(ToolConfirmationDecision::Deny);
1868 response_content = Some(Content {
1869 role: "function".to_string(),
1870 parts: vec![Part::FunctionResponse {
1871 function_response: FunctionResponseData::new(
1872 name.clone(),
1873 serde_json::json!({
1874 "error": format!("Tool '{}' execution denied by confirmation policy", name)
1875 }),
1876 ),
1877 id: id.clone(),
1878 annotations: None,
1879 }],
1880 });
1881 run_after_tool_callbacks = false;
1882 }
1883 None => {
1884 response_content = Some(Content {
1885 role: "function".to_string(),
1886 parts: vec![Part::FunctionResponse {
1887 function_response: FunctionResponseData::new(
1888 name.clone(),
1889 serde_json::json!({
1890 "error": format!("Tool '{}' requires confirmation", name)
1891 }),
1892 ),
1893 id: id.clone(),
1894 annotations: None,
1895 }],
1896 });
1897 run_after_tool_callbacks = false;
1898 }
1899 }
1900 }
1901
1902 #[allow(unused_mut)]
1905 let mut final_args = args.clone();
1906
1907 #[cfg(feature = "enhanced-plugins")]
1909 if response_content.is_none()
1910 && let Some(epm) = self.enhanced_plugin_manager.as_ref()
1911 && let Some(tool_ref) = self.tool_map.get(&name)
1912 {
1913 match epm
1914 .run_before_tool_call(
1915 tool_ref.clone(),
1916 final_args.clone(),
1917 self.ctx.clone() as Arc<dyn CallbackContext>,
1918 )
1919 .await
1920 {
1921 Ok(BeforeToolCallResult::Continue(modified_args)) => {
1922 final_args = modified_args;
1923 }
1924 Ok(BeforeToolCallResult::ShortCircuit(synthetic_result)) => {
1925 response_content = Some(Content {
1927 role: "function".to_string(),
1928 parts: vec![Part::FunctionResponse {
1929 function_response: FunctionResponseData::from_tool_result(
1930 name.clone(),
1931 synthetic_result,
1932 ),
1933 id: id.clone(),
1934 annotations: None,
1935 }],
1936 });
1937 executed_tool = Some(tool_ref.clone());
1938 }
1939 Err(e) => {
1940 response_content = Some(Content {
1941 role: "function".to_string(),
1942 parts: vec![Part::FunctionResponse {
1943 function_response: FunctionResponseData::new(
1944 name.clone(),
1945 serde_json::json!({ "error": e.to_string() }),
1946 ),
1947 id: id.clone(),
1948 annotations: None,
1949 }],
1950 });
1951 run_after_tool_callbacks = false;
1952 }
1953 }
1954 }
1955
1956 if response_content.is_none() {
1957 let tool_ctx = Arc::new(ToolCallbackContext::new(
1958 self.ctx.clone(),
1959 name.clone(),
1960 final_args.clone(),
1961 ));
1962 for callback in self.before_tool_callbacks.as_ref() {
1963 match callback(tool_ctx.clone() as Arc<dyn CallbackContext>).await {
1964 Ok(Some(c)) => {
1965 response_content = Some(c);
1966 break;
1967 }
1968 Ok(None) => continue,
1969 Err(e) => {
1970 response_content = Some(Content {
1971 role: "function".to_string(),
1972 parts: vec![Part::FunctionResponse {
1973 function_response: FunctionResponseData::new(
1974 name.clone(),
1975 serde_json::json!({ "error": e.to_string() }),
1976 ),
1977 id: id.clone(),
1978 annotations: None,
1979 }],
1980 });
1981 run_after_tool_callbacks = false;
1982 break;
1983 }
1984 }
1985 }
1986 }
1987
1988 if response_content.is_none() {
1990 let guard = self.cb_mutex.lock().unwrap_or_else(|e| e.into_inner());
1991 if let Some(ref cb_state) = *guard
1992 && cb_state.is_open(&name)
1993 {
1994 let msg = format!(
1995 "Tool '{}' is temporarily disabled after {} consecutive failures",
1996 name, cb_state.threshold
1997 );
1998 tracing::warn!(tool.name = %name, "circuit breaker open, skipping tool execution");
1999 response_content = Some(Content {
2000 role: "function".to_string(),
2001 parts: vec![Part::FunctionResponse {
2002 function_response: FunctionResponseData::new(
2003 name.clone(),
2004 serde_json::json!({ "error": msg }),
2005 ),
2006 id: id.clone(),
2007 annotations: None,
2008 }],
2009 });
2010 run_after_tool_callbacks = false;
2011 }
2012 drop(guard);
2013 }
2014
2015 if response_content.is_none() {
2017 if let Some(tool) = self.tool_map.get(&name) {
2018 let tool_ctx: Arc<dyn ToolContext> = Arc::new(
2019 AgentToolContext::new(self.ctx.clone(), function_call_id.clone())
2020 .with_tool_name(tool.name())
2021 .with_progress(self.progress_tx.clone()),
2022 );
2023 let span_name = format!("execute_tool {name}");
2024 let tool_span = tracing::info_span!(
2025 "",
2026 otel.name = %span_name,
2027 tool.name = %name,
2028 "gcp.vertex.agent.event_id" = %format!("{}_{}", self.invocation_id, name),
2029 "gcp.vertex.agent.invocation_id" = %self.invocation_id,
2030 "gcp.vertex.agent.session_id" = %self.ctx.session_id(),
2031 "gen_ai.conversation.id" = %self.ctx.session_id()
2032 );
2033
2034 let budget =
2035 self.tool_retry_budgets.get(&name).or(self.default_retry_budget.as_ref());
2036 let max_attempts = budget.map(|b| b.max_retries + 1).unwrap_or(1);
2037 let retry_delay = budget.map(|b| b.delay).unwrap_or_default();
2038
2039 let tool_clone = tool.clone();
2040 let tool_start = std::time::Instant::now();
2041 let mut last_error = String::new();
2042 let mut final_attempt: u32 = 0;
2043 let mut retry_result: Option<serde_json::Value> = None;
2044
2045 for attempt in 0..max_attempts {
2046 final_attempt = attempt;
2047 if attempt > 0 {
2048 tokio::time::sleep(retry_delay).await;
2049 }
2050 match async {
2051 let args_payload = trace_json_payload(
2052 &final_args,
2053 self.ctx.run_config().record_payloads,
2054 self.ctx.run_config().trace_payload_max_bytes,
2055 );
2056 tracing::debug!(tool.name = %name, tool.args = %args_payload, attempt = attempt, "tool_call");
2057 let exec_future = tool_clone.execute(tool_ctx.clone(), final_args.clone());
2058 let unwind_safe_future = std::panic::AssertUnwindSafe(
2059 tokio::time::timeout(self.tool_timeout, exec_future),
2060 );
2061 match futures::FutureExt::catch_unwind(unwind_safe_future).await {
2062 Ok(result) => result,
2063 Err(_panic) => Ok(Err(adk_core::AdkError::tool(format!(
2064 "tool '{}' panicked during execution",
2065 name
2066 )))),
2067 }
2068 }
2069 .instrument(tool_span.clone())
2070 .await
2071 {
2072 Ok(Ok(value)) => {
2073 let result_payload = trace_json_payload(
2074 &value,
2075 self.ctx.run_config().record_payloads,
2076 self.ctx.run_config().trace_payload_max_bytes,
2077 );
2078 tracing::debug!(tool.name = %name, tool.result = %result_payload, "tool_result");
2079 retry_result = Some(value);
2080 break;
2081 }
2082 Ok(Err(e)) => {
2083 last_error = e.to_string();
2084 if attempt + 1 < max_attempts {
2085 tracing::warn!(tool.name = %name, attempt = attempt, error = %last_error, "tool execution failed, retrying");
2086 } else {
2087 tracing::warn!(tool.name = %name, error = %last_error, "tool_error");
2088 }
2089 }
2090 Err(_) => {
2091 last_error = format!(
2092 "Tool '{}' timed out after {} seconds",
2093 name,
2094 self.tool_timeout.as_secs()
2095 );
2096 if attempt + 1 < max_attempts {
2097 tracing::warn!(tool.name = %name, attempt = attempt, timeout_secs = self.tool_timeout.as_secs(), "tool timed out, retrying");
2098 } else {
2099 tracing::warn!(tool.name = %name, timeout_secs = self.tool_timeout.as_secs(), "tool_timeout");
2100 }
2101 }
2102 }
2103 }
2104
2105 let tool_duration = tool_start.elapsed();
2106 let (tool_success, tool_error_message, function_response) = match retry_result {
2107 Some(value) => (true, None, value),
2108 None => (
2109 false,
2110 Some(last_error.clone()),
2111 serde_json::json!({ "error": last_error }),
2112 ),
2113 };
2114
2115 let outcome = ToolOutcome {
2116 tool_name: name.clone(),
2117 tool_args: final_args.clone(),
2118 success: tool_success,
2119 duration: tool_duration,
2120 error_message: tool_error_message.clone(),
2121 attempt: final_attempt,
2122 };
2123 tool_outcome_for_callback = Some(outcome);
2124
2125 {
2127 let mut guard = self.cb_mutex.lock().unwrap_or_else(|e| e.into_inner());
2128 if let Some(ref mut cb_state) = *guard {
2129 cb_state.record(tool_outcome_for_callback.as_ref().unwrap());
2130 }
2131 }
2132
2133 let final_function_response = if !tool_success {
2135 let mut fallback_result = None;
2136 let error_msg = tool_error_message.clone().unwrap_or_default();
2137 for callback in self.on_tool_error_callbacks.as_ref() {
2138 match callback(
2139 self.ctx.clone() as Arc<dyn CallbackContext>,
2140 tool.clone(),
2141 final_args.clone(),
2142 error_msg.clone(),
2143 )
2144 .await
2145 {
2146 Ok(Some(result)) => {
2147 fallback_result = Some(result);
2148 break;
2149 }
2150 Ok(None) => continue,
2151 Err(e) => {
2152 tracing::warn!(error = %e, "on_tool_error callback failed");
2153 break;
2154 }
2155 }
2156 }
2157 fallback_result.unwrap_or(function_response)
2158 } else {
2159 function_response
2160 };
2161
2162 let confirmation_decision = tool_actions.tool_confirmation_decision;
2163 tool_actions = tool_ctx.actions();
2164 if tool_actions.tool_confirmation_decision.is_none() {
2165 tool_actions.tool_confirmation_decision = confirmation_decision;
2166 }
2167 executed_tool = Some(tool.clone());
2168 executed_tool_response = Some(final_function_response.clone());
2169 response_content = Some(Content {
2170 role: "function".to_string(),
2171 parts: vec![Part::FunctionResponse {
2172 function_response: FunctionResponseData::from_tool_result(
2173 name.clone(),
2174 final_function_response,
2175 ),
2176 id: id.clone(),
2177 annotations: None,
2178 }],
2179 });
2180 } else {
2181 response_content = Some(Content {
2182 role: "function".to_string(),
2183 parts: vec![Part::FunctionResponse {
2184 function_response: FunctionResponseData::new(
2185 name.clone(),
2186 serde_json::json!({
2187 "error": format!("Tool {} not found", name)
2188 }),
2189 ),
2190 id: id.clone(),
2191 annotations: None,
2192 }],
2193 });
2194 }
2195 }
2196
2197 let mut response_content = response_content.expect("tool response content is set");
2199 if run_after_tool_callbacks {
2200 let outcome_ctx: Arc<dyn CallbackContext> = match tool_outcome_for_callback {
2201 Some(outcome) => Arc::new(ToolOutcomeCallbackContext {
2202 inner: self.ctx.clone() as Arc<dyn CallbackContext>,
2203 outcome,
2204 }),
2205 None => self.ctx.clone() as Arc<dyn CallbackContext>,
2206 };
2207 let cb_ctx: Arc<dyn CallbackContext> =
2208 Arc::new(ToolCallbackContext::new(outcome_ctx, name.clone(), final_args.clone()));
2209 for callback in self.after_tool_callbacks.as_ref() {
2210 match callback(cb_ctx.clone()).await {
2211 Ok(Some(modified)) => {
2212 response_content = modified;
2213 break;
2214 }
2215 Ok(None) => continue,
2216 Err(e) => {
2217 response_content = Content {
2218 role: "function".to_string(),
2219 parts: vec![Part::FunctionResponse {
2220 function_response: FunctionResponseData::new(
2221 name.clone(),
2222 serde_json::json!({ "error": e.to_string() }),
2223 ),
2224 id: id.clone(),
2225 annotations: None,
2226 }],
2227 };
2228 break;
2229 }
2230 }
2231 }
2232 if let (Some(tool_ref), Some(tool_resp)) = (&executed_tool, executed_tool_response) {
2233 for callback in self.after_tool_callbacks_full.as_ref() {
2234 match callback(
2235 cb_ctx.clone(),
2236 tool_ref.clone(),
2237 final_args.clone(),
2238 tool_resp.clone(),
2239 )
2240 .await
2241 {
2242 Ok(Some(modified_value)) => {
2243 response_content = Content {
2244 role: "function".to_string(),
2245 parts: vec![Part::FunctionResponse {
2246 function_response: FunctionResponseData::from_tool_result(
2247 name.clone(),
2248 modified_value,
2249 ),
2250 id: id.clone(),
2251 annotations: None,
2252 }],
2253 };
2254 break;
2255 }
2256 Ok(None) => continue,
2257 Err(e) => {
2258 response_content = Content {
2259 role: "function".to_string(),
2260 parts: vec![Part::FunctionResponse {
2261 function_response: FunctionResponseData::new(
2262 name.clone(),
2263 serde_json::json!({ "error": e.to_string() }),
2264 ),
2265 id: id.clone(),
2266 annotations: None,
2267 }],
2268 };
2269 break;
2270 }
2271 }
2272 }
2273 }
2274
2275 #[cfg(feature = "enhanced-plugins")]
2278 if let Some(epm) = self.enhanced_plugin_manager.as_ref()
2279 && let Some(tool_ref) = &executed_tool
2280 {
2281 let result_value = response_content
2283 .parts
2284 .iter()
2285 .find_map(|p| {
2286 if let Part::FunctionResponse { function_response, .. } = p {
2287 Some(function_response.response.clone())
2288 } else {
2289 None
2290 }
2291 })
2292 .unwrap_or(serde_json::json!(null));
2293
2294 match epm
2295 .run_after_tool_call(
2296 tool_ref.clone(),
2297 &final_args,
2298 result_value,
2299 self.ctx.clone() as Arc<dyn CallbackContext>,
2300 )
2301 .await
2302 {
2303 Ok(adk_plugin::AfterToolCallResult::Continue(modified_result)) => {
2304 response_content = Content {
2305 role: "function".to_string(),
2306 parts: vec![Part::FunctionResponse {
2307 function_response: FunctionResponseData::from_tool_result(
2308 name.clone(),
2309 modified_result,
2310 ),
2311 id: id.clone(),
2312 annotations: None,
2313 }],
2314 };
2315 }
2316 Err(e) => {
2317 response_content = Content {
2318 role: "function".to_string(),
2319 parts: vec![Part::FunctionResponse {
2320 function_response: FunctionResponseData::new(
2321 name.clone(),
2322 serde_json::json!({ "error": e.to_string() }),
2323 ),
2324 id: id.clone(),
2325 annotations: None,
2326 }],
2327 };
2328 }
2329 }
2330 }
2331 }
2332
2333 let escalate_or_skip = tool_actions.escalate || tool_actions.skip_summarization;
2334 ToolExecutionResult {
2335 index,
2336 content: response_content,
2337 actions: tool_actions,
2338 escalate_or_skip,
2339 }
2340 }
2341}
2342
2343#[async_trait]
2344impl Agent for LlmAgent {
2345 fn name(&self) -> &str {
2346 &self.name
2347 }
2348
2349 fn description(&self) -> &str {
2350 &self.description
2351 }
2352
2353 fn sub_agents(&self) -> &[Arc<dyn Agent>] {
2354 &self.sub_agents
2355 }
2356
2357 fn capabilities(&self) -> adk_core::AgentCapabilities {
2358 adk_core::AgentCapabilities {
2359 runtime_tools: true,
2360 handoff: true,
2361 relationship_confirmation: true,
2362 checkpoint_resume: false,
2363 shared_state: true,
2364 invocation_metadata: true,
2365 }
2366 }
2367
2368 #[adk_telemetry::instrument(
2369 skip(self, ctx),
2370 fields(
2371 agent.name = %self.name,
2372 agent.description = %self.description,
2373 invocation.id = %ctx.invocation_id(),
2374 user.id = %ctx.user_id(),
2375 session.id = %ctx.session_id()
2376 )
2377 )]
2378 async fn run(&self, ctx: Arc<dyn InvocationContext>) -> Result<adk_core::EventStream> {
2379 adk_telemetry::info!("Starting agent execution");
2380 let ctx = Self::apply_input_guardrails(ctx, self.input_guardrails.clone()).await?;
2381
2382 let agent_name = self.name.clone();
2383 let invocation_id = ctx.invocation_id().to_string();
2384 let model = self.model.clone();
2385 let prompt_config = PromptConfig::from_agent(self);
2386 let tool_setup = ToolSetup::from_agent(self);
2387 let output_key = self.output_key.clone();
2388 let output_max_retries = self.output_max_retries;
2389 let generate_content_config = self.generate_content_config.clone();
2390 let max_iterations = self.max_iterations;
2391 let tool_timeout = self.tool_timeout;
2392 let before_agent_callbacks = self.before_callbacks.clone();
2394 let after_agent_callbacks = self.after_callbacks.clone();
2395 let before_model_callbacks = self.before_model_callbacks.clone();
2396 let after_model_callbacks = self.after_model_callbacks.clone();
2397 let before_tool_callbacks = self.before_tool_callbacks.clone();
2398 let after_tool_callbacks = self.after_tool_callbacks.clone();
2399 let on_tool_error_callbacks = self.on_tool_error_callbacks.clone();
2400 let after_tool_callbacks_full = self.after_tool_callbacks_full.clone();
2401 let default_retry_budget = self.default_retry_budget.clone();
2402 let tool_retry_budgets = self.tool_retry_budgets.clone();
2403 let circuit_breaker_threshold = self.circuit_breaker_threshold;
2404 let tool_confirmation_policy = self.tool_confirmation_policy.clone();
2405 let tool_guardrails = Arc::clone(&self.tool_guardrails);
2406 let output_guardrails = self.output_guardrails.clone();
2407 let agent_tool_execution_strategy = self.tool_execution_strategy;
2408 #[cfg(feature = "enhanced-plugins")]
2409 let enhanced_plugin_manager = self.enhanced_plugin_manager.clone();
2410
2411 let s = stream! {
2412 let confirmation_decisions =
2413 ctx.run_config().tool_confirmation_decisions.clone();
2414 let confirmation_fingerprints =
2415 ctx.run_config().tool_confirmation_fingerprints.clone();
2416 let mut live_confirmation_decisions =
2417 std::collections::HashMap::<String, ToolConfirmationDecision>::new();
2418 let confirmation_handler = ctx.run_config().tool_confirmation_handler.clone();
2419
2420 for callback in before_agent_callbacks.as_ref() {
2424 match callback(ctx.clone() as Arc<dyn CallbackContext>).await {
2425 Ok(Some(content)) => {
2426 let mut early_event = Event::new(&invocation_id);
2428 early_event.author = agent_name.clone();
2429 early_event.llm_response.content = Some(content);
2430 yield Ok(early_event);
2431
2432 for after_callback in after_agent_callbacks.as_ref() {
2434 match after_callback(ctx.clone() as Arc<dyn CallbackContext>).await {
2435 Ok(Some(after_content)) => {
2436 let mut after_event = Event::new(&invocation_id);
2437 after_event.author = agent_name.clone();
2438 after_event.llm_response.content = Some(after_content);
2439 yield Ok(after_event);
2440 return;
2441 }
2442 Ok(None) => continue,
2443 Err(e) => {
2444 yield Err(e);
2445 return;
2446 }
2447 }
2448 }
2449 return;
2450 }
2451 Ok(None) => {
2452 continue;
2454 }
2455 Err(e) => {
2456 yield Err(e);
2458 return;
2459 }
2460 }
2461 }
2462
2463 let mut conversation_history = match prompt_config
2465 .prepare_conversation(&ctx, &agent_name)
2466 .await
2467 {
2468 Ok(history) => history,
2469 Err(error) => {
2470 yield Err(error);
2471 return;
2472 }
2473 };
2474
2475 let resolved_tools = match tool_setup.resolve(&ctx).await {
2476 Ok(tools) => tools,
2477 Err(error) => {
2478 yield Err(error);
2479 return;
2480 }
2481 };
2482 let tool_map = resolved_tools.map;
2483 let tool_declarations = resolved_tools.declarations;
2484 let valid_transfer_targets = resolved_tools.transfer_targets;
2485
2486 let collect_long_running_ids = |content: &Content| -> Vec<String> {
2487 content
2488 .parts
2489 .iter()
2490 .filter_map(|part| {
2491 if let Part::FunctionCall { name, .. } = part
2492 && let Some(tool) = tool_map.get(name)
2493 && tool.is_long_running()
2494 {
2495 return Some(name.clone());
2496 }
2497 None
2498 })
2499 .collect()
2500 };
2501
2502
2503 let mut circuit_breaker_state = circuit_breaker_threshold.map(CircuitBreakerState::new);
2506
2507 let mut last_interaction_id: Option<String> = None;
2516
2517 let mut iteration = 0;
2519 let mut schema_retry_count: usize = 0;
2520
2521 loop {
2522 if ctx.is_cancelled() {
2525 tracing::info!(agent.name = %agent_name, "invocation cancelled — stopping agent loop");
2526 return;
2527 }
2528 iteration += 1;
2529 if iteration > max_iterations {
2530 yield Err(adk_core::AdkError::agent(
2531 format!("Max iterations ({max_iterations}) exceeded")
2532 ));
2533 return;
2534 }
2535
2536 let config = build_generation_config(
2537 generate_content_config.as_ref(),
2538 prompt_config.output_schema.as_ref(),
2539 ctx.run_config().cached_content.as_deref(),
2540 );
2541
2542 let request = LlmRequest {
2543 model: model.name().to_string(),
2544 contents: conversation_history.clone(),
2545 tools: tool_declarations.clone(),
2546 config,
2547 previous_response_id: last_interaction_id.clone(),
2554 };
2555
2556 #[cfg(feature = "enhanced-plugins")]
2560 let (request, model_response_override_from_plugin) = {
2561 if let Some(epm) = &enhanced_plugin_manager {
2562 match epm.run_before_model_call(request, ctx.clone() as Arc<dyn CallbackContext>).await {
2563 Ok(BeforeModelCallResult::Continue(modified_request)) => {
2564 (modified_request, None)
2565 }
2566 Ok(BeforeModelCallResult::ShortCircuit(response)) => {
2567 (LlmRequest::new("", vec![]), Some(response))
2569 }
2570 Err(e) => {
2571 yield Err(e);
2572 return;
2573 }
2574 }
2575 } else {
2576 (request, None)
2577 }
2578 };
2579 #[cfg(not(feature = "enhanced-plugins"))]
2580 let model_response_override_from_plugin: Option<LlmResponse> = None;
2581
2582 let mut current_request = request;
2585 let mut model_response_override = model_response_override_from_plugin;
2586 if model_response_override.is_none() {
2587 for callback in before_model_callbacks.as_ref() {
2588 match callback(ctx.clone() as Arc<dyn CallbackContext>, current_request.clone()).await {
2589 Ok(BeforeModelResult::Continue(modified_request)) => {
2590 current_request = modified_request;
2592 }
2593 Ok(BeforeModelResult::Skip(response)) => {
2594 model_response_override = Some(response);
2596 break;
2597 }
2598 Err(e) => {
2599 yield Err(e);
2601 return;
2602 }
2603 }
2604 }
2605 }
2606 let request = current_request;
2607
2608 let mut accumulated_content: Option<Content> = None;
2610 let mut final_provider_metadata: Option<serde_json::Value> = None;
2611
2612 if let Some(cached_response) = model_response_override {
2613 accumulated_content = cached_response.content.clone();
2616 final_provider_metadata = cached_response.provider_metadata.clone();
2617 normalize_option_content(&mut accumulated_content);
2618 if let Some(content) = accumulated_content.take() {
2619 let has_function_calls = content
2620 .parts
2621 .iter()
2622 .any(|part| matches!(part, Part::FunctionCall { .. }));
2623 let content = if has_function_calls {
2624 content
2625 } else {
2626 Self::apply_output_guardrails(output_guardrails.as_ref(), content).await?
2627 };
2628 accumulated_content = Some(content);
2629 }
2630
2631 let mut cached_event = Event::new(&invocation_id);
2632 cached_event.author = agent_name.clone();
2633 cached_event.llm_response.content = accumulated_content.clone();
2634 cached_event.llm_response.provider_metadata = cached_response.provider_metadata.clone();
2635 cached_event.llm_response.interaction_id = cached_response.interaction_id.clone();
2637 if cached_response.interaction_id.is_some() {
2638 last_interaction_id = cached_response.interaction_id.clone();
2639 }
2640 cached_event.llm_request = Some(serde_json::to_string(&request).unwrap_or_default());
2641 cached_event.provider_metadata.insert("gcp.vertex.agent.llm_request".to_string(), serde_json::to_string(&request).unwrap_or_default());
2642 cached_event.provider_metadata.insert("gcp.vertex.agent.llm_response".to_string(), serde_json::to_string(&cached_response).unwrap_or_default());
2643
2644 if let Some(ref content) = accumulated_content {
2646 cached_event.long_running_tool_ids = collect_long_running_ids(content);
2647 }
2648
2649 yield Ok(cached_event);
2650 } else {
2651 let request_json = serde_json::to_string(&request).unwrap_or_default();
2653 let trace_request_json = trace_json_payload(
2654 &request,
2655 ctx.run_config().record_payloads,
2656 ctx.run_config().trace_payload_max_bytes,
2657 );
2658
2659 let llm_ts = std::time::SystemTime::now()
2661 .duration_since(std::time::UNIX_EPOCH)
2662 .unwrap_or_default()
2663 .as_nanos();
2664 let llm_event_id = format!("{}_llm_{}", invocation_id, llm_ts);
2665 let llm_span = tracing::info_span!(
2666 "call_llm",
2667 "gcp.vertex.agent.event_id" = %llm_event_id,
2668 "gcp.vertex.agent.invocation_id" = %invocation_id,
2669 "gcp.vertex.agent.session_id" = %ctx.session_id(),
2670 "gen_ai.conversation.id" = %ctx.session_id(),
2671 "gcp.vertex.agent.llm_request" = %trace_request_json,
2672 "gcp.vertex.agent.llm_response" = tracing::field::Empty );
2674 let _llm_guard = llm_span.enter();
2675
2676 use adk_core::StreamingMode;
2678 let streaming_mode = ctx.run_config().streaming_mode;
2679 let should_stream_to_client = matches!(streaming_mode, StreamingMode::SSE | StreamingMode::Bidi)
2680 && output_guardrails.is_empty();
2681
2682 let mut response_stream = model.generate_content(request, true).await?;
2684
2685 use futures::StreamExt;
2686
2687 let mut last_chunk: Option<LlmResponse> = None;
2689
2690 while let Some(chunk_result) = response_stream.next().await {
2692 if ctx.is_cancelled() {
2696 tracing::info!(agent.name = %agent_name, "invocation cancelled during LLM streaming");
2697 return;
2698 }
2699 let mut chunk = match chunk_result {
2700 Ok(c) => c,
2701 Err(e) => {
2702 yield Err(e);
2703 return;
2704 }
2705 };
2706
2707 for callback in after_model_callbacks.as_ref() {
2710 match callback(ctx.clone() as Arc<dyn CallbackContext>, chunk.clone()).await {
2711 Ok(Some(modified_chunk)) => {
2712 chunk = modified_chunk;
2714 break;
2715 }
2716 Ok(None) => {
2717 continue;
2719 }
2720 Err(e) => {
2721 yield Err(e);
2723 return;
2724 }
2725 }
2726 }
2727
2728 normalize_option_content(&mut chunk.content);
2729
2730 if let Some(chunk_content) = chunk.content.clone() {
2732 if let Some(ref mut acc) = accumulated_content {
2733 acc.parts.extend(chunk_content.parts);
2734 } else {
2735 accumulated_content = Some(chunk_content);
2736 }
2737 }
2738
2739 if should_stream_to_client {
2741 let long_running_tool_ids = chunk
2742 .content
2743 .as_ref()
2744 .map(&collect_long_running_ids)
2745 .unwrap_or_default();
2746 yield Ok(build_partial_llm_event(
2747 &llm_event_id,
2748 &invocation_id,
2749 &agent_name,
2750 &request_json,
2751 &chunk,
2752 long_running_tool_ids,
2753 ));
2754 }
2755
2756 if chunk.interaction_id.is_some() {
2760 last_interaction_id = chunk.interaction_id.clone();
2761 }
2762
2763 last_chunk = Some(chunk.clone());
2765
2766 if chunk.turn_complete {
2768 break;
2769 }
2770 }
2771
2772 if !should_stream_to_client {
2774 if let Some(content) = accumulated_content.take() {
2775 let has_function_calls = content
2776 .parts
2777 .iter()
2778 .any(|part| matches!(part, Part::FunctionCall { .. }));
2779 let content = if has_function_calls {
2780 content
2781 } else {
2782 Self::apply_output_guardrails(output_guardrails.as_ref(), content).await?
2783 };
2784 accumulated_content = Some(content);
2785 }
2786
2787 if let Some(last) = &last_chunk {
2788 final_provider_metadata = last.provider_metadata.clone();
2789 }
2790 let long_running_tool_ids = accumulated_content
2791 .as_ref()
2792 .map(&collect_long_running_ids)
2793 .unwrap_or_default();
2794 yield Ok(build_final_llm_event(
2795 &llm_event_id,
2796 &invocation_id,
2797 &agent_name,
2798 &request_json,
2799 accumulated_content.as_ref(),
2800 last_chunk.as_ref(),
2801 long_running_tool_ids,
2802 ));
2803 }
2804
2805 if let Some(ref last) = last_chunk
2816 && let Some(ref code) = last.error_code
2817 {
2818 let message = last
2819 .error_message
2820 .clone()
2821 .unwrap_or_else(|| "provider reported a terminal error".to_string());
2822 tracing::error!(
2823 error.code = %code,
2824 error.message = %message,
2825 agent = %agent_name,
2826 "model reported a terminal error"
2827 );
2828 let mut details = adk_core::ErrorDetails::default();
2835 details
2836 .metadata
2837 .insert("provider_error_code".to_string(), serde_json::json!(code));
2838 let provider_error = adk_core::AdkError::new(
2839 adk_core::ErrorComponent::Model,
2840 adk_core::ErrorCategory::Internal,
2841 "model.provider_error",
2842 format!("{code}: {message}"),
2843 )
2844 .with_details(details);
2845 yield Err(provider_error);
2846 return;
2847 }
2848
2849 if let Some(ref content) = accumulated_content {
2851 let response_json = trace_json_payload(
2852 content,
2853 ctx.run_config().record_payloads,
2854 ctx.run_config().trace_payload_max_bytes,
2855 );
2856 llm_span.record("gcp.vertex.agent.llm_response", &response_json);
2857 }
2858 }
2859
2860 #[cfg(feature = "enhanced-plugins")]
2864 if let Some(epm) = &enhanced_plugin_manager
2865 && let Some(ref content) = accumulated_content {
2866 let response_for_hook = LlmResponse {
2867 content: Some(content.clone()),
2868 provider_metadata: final_provider_metadata.clone(),
2869 ..Default::default()
2870 };
2871 match epm.run_after_model_call(response_for_hook, ctx.clone() as Arc<dyn CallbackContext>).await {
2872 Ok(adk_plugin::AfterModelCallResult::Continue(modified_response)) => {
2873 accumulated_content = modified_response.content;
2874 if modified_response.provider_metadata.is_some() {
2875 final_provider_metadata = modified_response.provider_metadata;
2876 }
2877 }
2878 Err(e) => {
2879 yield Err(e);
2880 return;
2881 }
2882 }
2883 }
2884
2885 let function_call_names: Vec<String> = accumulated_content.as_ref()
2887 .map(|c| c.parts.iter()
2888 .filter_map(|p| {
2889 if let Part::FunctionCall { name, .. } = p {
2890 Some(name.clone())
2891 } else {
2892 None
2893 }
2894 })
2895 .collect())
2896 .unwrap_or_default();
2897
2898 let has_function_calls = !function_call_names.is_empty();
2899
2900 let all_calls_are_long_running = has_function_calls && function_call_names.iter().all(|name| {
2904 tool_map.get(name)
2905 .map(|t| t.is_long_running())
2906 .unwrap_or(false)
2907 });
2908
2909 if let Some(ref content) = accumulated_content {
2911 conversation_history.push(Self::augment_content_for_history(
2912 content,
2913 final_provider_metadata.as_ref(),
2914 ));
2915
2916 if let Some(ref output_key) = output_key
2918 && !has_function_calls
2919 {
2920 let mut text_parts = String::new();
2921 for part in &content.parts {
2922 if let Part::Text { text } = part {
2923 text_parts.push_str(text);
2924 }
2925 }
2926 if !text_parts.is_empty() {
2927 let mut state_event = Event::new(&invocation_id);
2929 state_event.author = agent_name.clone();
2930 state_event.actions.state_delta.insert(
2931 output_key.clone(),
2932 serde_json::Value::String(text_parts),
2933 );
2934 yield Ok(state_event);
2935 }
2936 }
2937 }
2938
2939 if !has_function_calls {
2940 if let Some(schema) = &prompt_config.output_schema {
2945 let text = accumulated_content
2946 .as_ref()
2947 .map(|c| {
2948 c.parts
2949 .iter()
2950 .filter_map(|p| {
2951 if let Part::Text { text } = p {
2952 Some(text.as_str())
2953 } else {
2954 None
2955 }
2956 })
2957 .collect::<Vec<_>>()
2958 .join("")
2959 })
2960 .unwrap_or_default();
2961
2962 if !text.is_empty()
2963 && let Err(validation_error) = validate_output_against_schema(&text, schema)
2964 {
2965 if schema_retry_count >= output_max_retries {
2966 yield Err(adk_core::AdkError::agent(format!(
2967 "output schema validation failed after {} attempts",
2968 output_max_retries
2969 )));
2970 return;
2971 }
2972 schema_retry_count += 1;
2973
2974 let correction = format!(
2976 "Your output did not match the required schema. Error: {}. Please produce valid JSON matching the schema.",
2977 validation_error
2978 );
2979 conversation_history.push(Content {
2980 role: "user".to_string(),
2981 parts: vec![Part::Text { text: correction }],
2982 });
2983 continue;
2984 }
2985 }
2986
2987 if let Some(ref content) = accumulated_content {
2990 let response_json = trace_json_payload(
2991 content,
2992 ctx.run_config().record_payloads,
2993 ctx.run_config().trace_payload_max_bytes,
2994 );
2995 tracing::Span::current().record("gcp.vertex.agent.llm_response", &response_json);
2996 }
2997
2998 tracing::info!(agent.name = %agent_name, "Agent execution complete");
2999 break;
3000 }
3001
3002 if let Some(content) = &accumulated_content {
3004 let strategy = agent_tool_execution_strategy
3007 .unwrap_or(ToolExecutionStrategy::Sequential);
3008
3009 let fc_parts = collect_function_calls(content, &invocation_id);
3010
3011 let mut transfer_handled = false;
3015 for call in &fc_parts {
3016 if call.name == "transfer_to_agent" {
3017 let target_agent = call
3018 .args
3019 .get("agent_name")
3020 .and_then(|value| value.as_str())
3021 .unwrap_or_default()
3022 .to_string();
3023
3024 let valid_target = valid_transfer_targets.iter().any(|n| n == &target_agent);
3025 if !valid_target {
3026 let error_content = Content {
3027 role: "function".to_string(),
3028 parts: vec![Part::FunctionResponse {
3029 function_response: FunctionResponseData::new(
3030 call.name.clone(),
3031 serde_json::json!({
3032 "error": format!(
3033 "Agent '{}' not found. Available agents: {:?}",
3034 target_agent, valid_transfer_targets
3035 )
3036 }),
3037 ),
3038 id: call.id.clone(),
3039 annotations: None,
3040 }],
3041 };
3042 conversation_history.push(error_content.clone());
3043 let mut error_event = Event::new(&invocation_id);
3044 error_event.author = agent_name.clone();
3045 error_event.llm_response.content = Some(error_content);
3046 yield Ok(error_event);
3047 continue;
3048 }
3049
3050 let mut transfer_event = Event::new(&invocation_id);
3051 transfer_event.author = agent_name.clone();
3052 transfer_event.actions.transfer_to_agent = Some(target_agent);
3053 yield Ok(transfer_event);
3054 transfer_handled = true;
3055 break;
3056 }
3057 }
3058 if transfer_handled {
3059 return;
3060 }
3061
3062 let mut fc_parts: Vec<_> = fc_parts
3064 .into_iter()
3065 .filter(|call| {
3066 if call.name == "transfer_to_agent" {
3067 return false;
3068 }
3069 if let Some(tool) = tool_map.get(&call.name)
3070 && tool.is_builtin()
3071 {
3072 adk_telemetry::debug!(tool.name = %call.name, "skipping built-in tool execution");
3073 return false;
3074 }
3075 true
3076 })
3077 .collect();
3078
3079 for call in &mut fc_parts {
3083 match screen_tool_call(&tool_guardrails, &call.name, &call.args).await {
3084 ToolScreening::Allow(args) => call.args = args,
3085 ToolScreening::Deny(reason) => {
3086 call.guardrail_denial = Some(reason);
3087 }
3088 }
3089 }
3090
3091 let mut confirmation_interrupted = false;
3095 for call in &fc_parts {
3096 if call.guardrail_denial.is_none()
3097 && (tool_confirmation_policy.requires_confirmation(&call.name)
3098 || ctx.requires_tool_confirmation(&call.name))
3099 && static_confirmation_decision(
3100 &confirmation_decisions,
3101 &confirmation_fingerprints,
3102 &call.function_call_id,
3103 &call.name,
3104 &call.args,
3105 )
3106 .is_none()
3107 && live_confirmation_decisions
3108 .get(&call.function_call_id)
3109 .copied()
3110 .is_none()
3111 {
3112 let request = ToolConfirmationRequest {
3113 tool_name: call.name.clone(),
3114 function_call_id: Some(call.function_call_id.clone()),
3115 args: call.args.clone(),
3116 };
3117 if let Some(handler) = confirmation_handler.as_ref() {
3118 match handler.decide(&request).await {
3119 Ok(decision) => {
3120 live_confirmation_decisions
3121 .insert(call.function_call_id.clone(), decision);
3122 continue;
3123 }
3124 Err(error) => {
3125 yield Err(error);
3126 return;
3127 }
3128 }
3129 }
3130
3131 let mut ce = Event::new(&invocation_id);
3132 ce.author = agent_name.clone();
3133 ce.llm_response.interrupted = true;
3134 ce.llm_response.turn_complete = true;
3135 ce.llm_response.content = Some(Content {
3136 role: "model".to_string(),
3137 parts: vec![Part::Text {
3138 text: format!(
3139 "Tool confirmation required for '{}'. Provide approve/deny decision to continue.",
3140 call.name
3141 ),
3142 }],
3143 });
3144 ce.actions.tool_confirmation = Some(request);
3145 yield Ok(ce);
3146 confirmation_interrupted = true;
3147 break;
3148 }
3149 }
3150 if confirmation_interrupted {
3151 return;
3152 }
3153
3154 let cb_mutex = std::sync::Mutex::new(circuit_breaker_state.take());
3156
3157 let concurrency_manager = adk_core::ToolConcurrencyManager::new(
3160 &ctx.run_config().tool_concurrency,
3161 );
3162
3163 let (progress_tx, mut progress_rx) =
3168 tokio::sync::mpsc::channel::<Event>(TOOL_PROGRESS_CAPACITY);
3169
3170 let executor = ToolExecutor {
3171 ctx: ctx.clone(),
3172 tool_map: &tool_map,
3173 tool_retry_budgets: &tool_retry_budgets,
3174 default_retry_budget: &default_retry_budget,
3175 before_tool_callbacks: &before_tool_callbacks,
3176 after_tool_callbacks: &after_tool_callbacks,
3177 after_tool_callbacks_full: &after_tool_callbacks_full,
3178 on_tool_error_callbacks: &on_tool_error_callbacks,
3179 tool_confirmation_policy: &tool_confirmation_policy,
3180 cb_mutex: &cb_mutex,
3181 invocation_id: &invocation_id,
3182 concurrency_manager: &concurrency_manager,
3183 progress_tx: progress_tx.clone(),
3184 tool_timeout,
3185 confirmation_decisions: &confirmation_decisions,
3186 confirmation_fingerprints: &confirmation_fingerprints,
3187 live_confirmation_decisions: &live_confirmation_decisions,
3188 #[cfg(feature = "enhanced-plugins")]
3189 enhanced_plugin_manager: &enhanced_plugin_manager,
3190 };
3191
3192 if ctx.is_cancelled() {
3195 tracing::info!(agent.name = %agent_name, "invocation cancelled before tool dispatch");
3196 return;
3197 }
3198
3199 let mut results = {
3204 let dispatch = async {
3205 let results: Vec<ToolExecutionResult> = match strategy {
3206 ToolExecutionStrategy::Sequential => {
3207 let mut results = Vec::with_capacity(fc_parts.len());
3208 for call in fc_parts {
3209 results.push(executor.execute(call).await);
3210 }
3211 results
3212 }
3213 ToolExecutionStrategy::Parallel => {
3214 use futures::StreamExt as _;
3215 let buffer_size = fc_parts.len().max(1);
3222 futures::stream::iter(
3223 fc_parts.into_iter().map(|call| executor.execute(call)),
3224 )
3225 .buffer_unordered(buffer_size)
3226 .collect()
3227 .await
3228 }
3229 ToolExecutionStrategy::Auto => {
3230 let (concurrent_fcs, sequential_fcs): (Vec<_>, Vec<_>) =
3233 fc_parts.into_iter().partition(|call| {
3234 tool_map.get(&call.name).is_some_and(|tool| {
3235 tool.is_read_only() && tool.is_concurrency_safe()
3236 })
3237 });
3238 let mut all_results = Vec::new();
3239
3240 if !concurrent_fcs.is_empty() {
3243 use futures::StreamExt as _;
3244 let buffer_size = concurrent_fcs.len().max(1);
3245 all_results.extend(
3246 futures::stream::iter(
3247 concurrent_fcs
3248 .into_iter()
3249 .map(|call| executor.execute(call)),
3250 )
3251 .buffer_unordered(buffer_size)
3252 .collect::<Vec<_>>()
3253 .await,
3254 );
3255 }
3256
3257 for call in sequential_fcs {
3259 all_results.push(executor.execute(call).await);
3260 }
3261 all_results
3262 }
3263 };
3264 results
3265 };
3266
3267 tokio::pin!(dispatch);
3273 let results = loop {
3274 tokio::select! {
3275 biased;
3276 Some(progress_event) = progress_rx.recv() => {
3277 yield Ok(progress_event);
3278 }
3279 done = &mut dispatch => break done,
3280 }
3281 };
3282 while let Ok(progress_event) = progress_rx.try_recv() {
3284 yield Ok(progress_event);
3285 }
3286 results
3287 };
3288 results.sort_by_key(|r| r.index);
3290
3291 circuit_breaker_state = cb_mutex.into_inner().unwrap_or_else(|e| e.into_inner());
3293
3294 for result in results {
3296 let mut tool_event = Event::new(&invocation_id);
3297 tool_event.author = agent_name.clone();
3298 tool_event.actions = result.actions;
3299 tool_event.llm_response.content = Some(result.content.clone());
3300 yield Ok(tool_event);
3301
3302 if result.escalate_or_skip {
3303 return;
3304 }
3305
3306 conversation_history.push(result.content);
3307 }
3308 }
3309
3310 if all_calls_are_long_running {
3314 }
3318 }
3319
3320 for callback in after_agent_callbacks.as_ref() {
3323 match callback(ctx.clone() as Arc<dyn CallbackContext>).await {
3324 Ok(Some(content)) => {
3325 let mut after_event = Event::new(&invocation_id);
3327 after_event.author = agent_name.clone();
3328 after_event.llm_response.content = Some(content);
3329 yield Ok(after_event);
3330 break; }
3332 Ok(None) => {
3333 continue;
3335 }
3336 Err(e) => {
3337 yield Err(e);
3339 return;
3340 }
3341 }
3342 }
3343 };
3344
3345 Ok(Box::pin(s))
3346 }
3347}
3348
3349#[cfg(test)]
3350mod run_helper_tests {
3351 use super::*;
3352
3353 #[test]
3354 fn generation_config_layers_schema_and_cached_content() {
3355 let base =
3356 adk_core::GenerateContentConfig { temperature: Some(0.25), ..Default::default() };
3357 let schema = serde_json::json!({"type": "object"});
3358
3359 let config = build_generation_config(Some(&base), Some(&schema), Some("cached/example"))
3360 .expect("config should be present");
3361
3362 assert_eq!(config.temperature, Some(0.25));
3363 assert_eq!(config.response_schema, Some(schema));
3364 assert_eq!(config.cached_content.as_deref(), Some("cached/example"));
3365 }
3366
3367 #[test]
3368 fn function_calls_preserve_order_and_create_fallback_ids() {
3369 let content = Content {
3370 role: "model".to_string(),
3371 parts: vec![
3372 Part::Text { text: "before".to_string() },
3373 Part::FunctionCall {
3374 name: "first".to_string(),
3375 args: serde_json::json!({"value": 1}),
3376 id: None,
3377 thought_signature: None,
3378 },
3379 Part::FunctionCall {
3380 name: "second".to_string(),
3381 args: serde_json::json!({"value": 2}),
3382 id: Some("provider-id".to_string()),
3383 thought_signature: None,
3384 },
3385 ],
3386 };
3387
3388 let calls = collect_function_calls(&content, "invocation");
3389
3390 assert_eq!(calls.len(), 2);
3391 assert_eq!(calls[0].index, 0);
3392 assert_eq!(calls[0].name, "first");
3393 assert_eq!(calls[0].function_call_id, "invocation_first_0");
3394 assert_eq!(calls[1].index, 1);
3395 assert_eq!(calls[1].name, "second");
3396 assert_eq!(calls[1].function_call_id, "provider-id");
3397 }
3398}