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 collect_long_running_tool_ids(
120 tool_map: &HashMap<String, Arc<dyn Tool>>,
121 content: &Content,
122) -> Vec<String> {
123 content
124 .parts
125 .iter()
126 .filter_map(|part| {
127 if let Part::FunctionCall { name, .. } = part
128 && tool_map.get(name).is_some_and(|tool| tool.is_long_running())
129 {
130 return Some(name.clone());
131 }
132 None
133 })
134 .collect()
135}
136
137fn build_partial_llm_event(
138 event_id: &str,
139 invocation_id: &str,
140 agent_name: &str,
141 request_json: &str,
142 chunk: &LlmResponse,
143 long_running_tool_ids: Vec<String>,
144) -> Event {
145 let mut event = Event::with_id(event_id, invocation_id);
146 event.author = agent_name.to_string();
147 if !chunk.partial {
152 event.llm_request = Some(request_json.to_string());
153 event
154 .provider_metadata
155 .insert("gcp.vertex.agent.llm_request".to_string(), request_json.to_string());
156 event.provider_metadata.insert(
157 "gcp.vertex.agent.llm_response".to_string(),
158 serde_json::to_string(chunk).unwrap_or_default(),
159 );
160 }
161 event.llm_response.partial = chunk.partial;
162 event.llm_response.turn_complete = chunk.turn_complete;
163 event.llm_response.finish_reason = chunk.finish_reason;
164 event.llm_response.usage_metadata = chunk.usage_metadata.clone();
165 event.llm_response.citation_metadata = chunk.citation_metadata.clone();
166 event.llm_response.content = chunk.content.clone();
167 event.llm_response.provider_metadata = chunk.provider_metadata.clone();
168 event.llm_response.interaction_id = chunk.interaction_id.clone();
169 event.llm_response.interrupted = chunk.interrupted;
172 event.llm_response.error_code = chunk.error_code.clone();
173 event.llm_response.error_message = chunk.error_message.clone();
174 event.long_running_tool_ids = long_running_tool_ids;
175 event
176}
177
178fn build_final_llm_event(
179 event_id: &str,
180 invocation_id: &str,
181 agent_name: &str,
182 request_json: &str,
183 content: Option<&Content>,
184 last_chunk: Option<&LlmResponse>,
185 long_running_tool_ids: Vec<String>,
186) -> Event {
187 let mut event = Event::with_id(event_id, invocation_id);
188 event.author = agent_name.to_string();
189 event.llm_request = Some(request_json.to_string());
190 event
191 .provider_metadata
192 .insert("gcp.vertex.agent.llm_request".to_string(), request_json.to_string());
193 event.llm_response.content = content.cloned();
194 event.llm_response.partial = false;
195 event.llm_response.turn_complete = true;
196
197 if let Some(last_chunk) = last_chunk {
198 event.llm_response.turn_complete = last_chunk.turn_complete;
199 event.llm_response.finish_reason = last_chunk.finish_reason;
200 event.llm_response.usage_metadata = last_chunk.usage_metadata.clone();
201 event.llm_response.citation_metadata = last_chunk.citation_metadata.clone();
202 event.llm_response.provider_metadata = last_chunk.provider_metadata.clone();
203 event.llm_response.interaction_id = last_chunk.interaction_id.clone();
204 event.llm_response.interrupted = last_chunk.interrupted;
205 event.llm_response.error_code = last_chunk.error_code.clone();
206 event.llm_response.error_message = last_chunk.error_message.clone();
207 event.provider_metadata.insert(
208 "gcp.vertex.agent.llm_response".to_string(),
209 serde_json::to_string(last_chunk).unwrap_or_default(),
210 );
211 }
212
213 event.long_running_tool_ids = long_running_tool_ids;
214 event
215}
216
217pub struct LlmAgent {
225 name: String,
226 description: String,
227 model: Arc<dyn Llm>,
228 instruction: Option<String>,
229 instruction_provider: Option<Arc<InstructionProvider>>,
230 global_instruction: Option<String>,
231 global_instruction_provider: Option<Arc<GlobalInstructionProvider>>,
232 skills_index: Option<Arc<SkillIndex>>,
233 skill_policy: SelectionPolicy,
234 max_skill_chars: usize,
235 #[allow(dead_code)] input_schema: Option<serde_json::Value>,
237 output_schema: Option<serde_json::Value>,
238 output_max_retries: usize,
240 disallow_transfer_to_parent: bool,
241 disallow_transfer_to_peers: bool,
242 include_contents: adk_core::IncludeContents,
243 tools: Vec<Arc<dyn Tool>>,
244 toolsets: Vec<Arc<dyn Toolset>>,
245 sub_agents: Vec<Arc<dyn Agent>>,
246 output_key: Option<String>,
247 generate_content_config: Option<adk_core::GenerateContentConfig>,
249 max_iterations: u32,
251 tool_timeout: std::time::Duration,
253 before_callbacks: Arc<Vec<BeforeAgentCallback>>,
254 after_callbacks: Arc<Vec<AfterAgentCallback>>,
255 before_model_callbacks: Arc<Vec<BeforeModelCallback>>,
256 after_model_callbacks: Arc<Vec<AfterModelCallback>>,
257 before_tool_callbacks: Arc<Vec<BeforeToolCallback>>,
258 after_tool_callbacks: Arc<Vec<AfterToolCallback>>,
259 on_tool_error_callbacks: Arc<Vec<OnToolErrorCallback>>,
260 after_tool_callbacks_full: Arc<Vec<AfterToolCallbackFull>>,
262 default_retry_budget: Option<RetryBudget>,
264 tool_retry_budgets: std::collections::HashMap<String, RetryBudget>,
266 circuit_breaker_threshold: Option<u32>,
269 tool_confirmation_policy: ToolConfirmationPolicy,
270 tool_execution_strategy: Option<ToolExecutionStrategy>,
273 parallelize_agent_delegations: bool,
275 input_guardrails: Arc<GuardrailSet>,
276 output_guardrails: Arc<GuardrailSet>,
277 tool_guardrails: Arc<ToolGuardrailSet>,
278 #[cfg(feature = "enhanced-plugins")]
281 enhanced_plugin_manager: Option<Arc<EnhancedPluginManager>>,
282 #[cfg(feature = "sandbox")]
286 sandbox_config: Option<adk_sandbox::workspace::SandboxConfig>,
287}
288
289struct PromptConfig {
290 instruction: Option<String>,
291 instruction_provider: Option<Arc<InstructionProvider>>,
292 global_instruction: Option<String>,
293 global_instruction_provider: Option<Arc<GlobalInstructionProvider>>,
294 skills_index: Option<Arc<SkillIndex>>,
295 skill_policy: SelectionPolicy,
296 max_skill_chars: usize,
297 output_schema: Option<serde_json::Value>,
298 include_contents: adk_core::IncludeContents,
299}
300
301impl PromptConfig {
302 fn from_agent(agent: &LlmAgent) -> Self {
303 Self {
304 instruction: agent.instruction.clone(),
305 instruction_provider: agent.instruction_provider.clone(),
306 global_instruction: agent.global_instruction.clone(),
307 global_instruction_provider: agent.global_instruction_provider.clone(),
308 skills_index: agent.skills_index.clone(),
309 skill_policy: agent.skill_policy.clone(),
310 max_skill_chars: agent.max_skill_chars,
311 output_schema: agent.output_schema.clone(),
312 include_contents: agent.include_contents,
313 }
314 }
315
316 async fn prepare_conversation(
317 &self,
318 ctx: &Arc<dyn InvocationContext>,
319 agent_name: &str,
320 ) -> Result<Vec<Content>> {
321 let mut preamble = Vec::new();
322
323 if let Some(provider) = &self.global_instruction_provider {
324 let instruction = provider(ctx.clone() as Arc<dyn ReadonlyContext>).await?;
325 if !instruction.is_empty() {
326 preamble.push(Content::new("user").with_text(instruction));
327 }
328 } else if let Some(template) = &self.global_instruction {
329 let instruction = adk_core::inject_session_state(ctx.as_ref(), template).await?;
330 if !instruction.is_empty() {
331 preamble.push(Content::new("user").with_text(instruction));
332 }
333 }
334
335 if let Some(provider) = &self.instruction_provider {
336 let instruction = provider(ctx.clone() as Arc<dyn ReadonlyContext>).await?;
337 if !instruction.is_empty() {
338 preamble.push(Content::new("user").with_text(instruction));
339 }
340 } else if let Some(template) = &self.instruction {
341 let instruction = adk_core::inject_session_state(ctx.as_ref(), template).await?;
342 if !instruction.is_empty() {
343 preamble.push(Content::new("user").with_text(instruction));
344 }
345 }
346
347 if let Some(schema) = &self.output_schema {
348 preamble.push(Content::new("user").with_text(format!(
349 "You MUST respond with valid JSON conforming to this schema: {schema}. Do not include any text outside the JSON object."
350 )));
351 }
352
353 let agent_filter = if ctx.authoritative_transfer_targets()
354 || !ctx.run_config().transfer_targets.is_empty()
355 {
356 Some(agent_name)
357 } else {
358 None
359 };
360 let mut session_history =
361 ctx.session().conversation_history_scoped(agent_filter, ctx.branch());
362 let mut current_user_content = ctx.user_content().clone();
363 if let Some(index) = &self.skills_index {
364 apply_skill_injection(
365 &mut current_user_content,
366 index.as_ref(),
367 &self.skill_policy,
368 self.max_skill_chars,
369 );
370 }
371 if let Some(index) = session_history.iter().rposition(|content| content.role == "user") {
372 session_history[index] = current_user_content.clone();
373 } else {
374 session_history.push(current_user_content.clone());
375 }
376
377 Ok(match self.include_contents {
378 adk_core::IncludeContents::None => {
379 preamble.push(current_user_content);
380 preamble
381 }
382 adk_core::IncludeContents::Default => {
383 preamble.extend(session_history);
384 preamble
385 }
386 })
387 }
388}
389
390struct ToolSetup {
391 tools: Vec<Arc<dyn Tool>>,
392 toolsets: Vec<Arc<dyn Toolset>>,
393 sub_agents: Vec<Arc<dyn Agent>>,
394 disallow_transfer_to_parent: bool,
395 disallow_transfer_to_peers: bool,
396}
397
398struct ResolvedTools {
399 map: HashMap<String, Arc<dyn Tool>>,
400 declarations: HashMap<String, serde_json::Value>,
401 transfer_targets: Vec<String>,
402}
403
404impl ToolSetup {
405 fn from_agent(agent: &LlmAgent) -> Self {
406 Self {
407 tools: agent.tools.clone(),
408 toolsets: agent.toolsets.clone(),
409 sub_agents: agent.sub_agents.clone(),
410 disallow_transfer_to_parent: agent.disallow_transfer_to_parent,
411 disallow_transfer_to_peers: agent.disallow_transfer_to_peers,
412 }
413 }
414
415 async fn resolve(&self, ctx: &Arc<dyn InvocationContext>) -> Result<ResolvedTools> {
416 let mut tools = self.tools.clone();
417 let static_tool_names: std::collections::HashSet<_> =
418 tools.iter().map(|tool| tool.name().to_string()).collect();
419 let mut toolset_sources = std::collections::HashMap::<String, String>::new();
420 let mut active_toolsets: Vec<&dyn Toolset> =
421 self.toolsets.iter().map(AsRef::as_ref).collect();
422 active_toolsets.extend(
423 ctx.run_config().runtime_toolsets.iter().map(|runtime| runtime.toolset().as_ref()),
424 );
425
426 for toolset in active_toolsets {
427 for tool in toolset.tools(ctx.clone() as Arc<dyn ReadonlyContext>).await? {
428 let name = tool.name().to_string();
429 if static_tool_names.contains(&name) {
430 return Err(adk_core::AdkError::agent(format!(
431 "Duplicate tool name '{name}': conflict between static tool and toolset '{}'",
432 toolset.name()
433 )));
434 }
435 if let Some(other_toolset) = toolset_sources.get(&name) {
436 return Err(adk_core::AdkError::agent(format!(
437 "Duplicate tool name '{name}': conflict between toolset '{other_toolset}' and toolset '{}'",
438 toolset.name()
439 )));
440 }
441 toolset_sources.insert(name, toolset.name().to_string());
442 tools.push(tool);
443 }
444 }
445
446 let map = tools.iter().map(|tool| (tool.name().to_string(), tool.clone())).collect();
447 let mut declarations = tools
448 .iter()
449 .map(|tool| (tool.name().to_string(), tool.declaration()))
450 .collect::<std::collections::HashMap<_, _>>();
451 let mut transfer_targets: Vec<String> = if ctx.authoritative_transfer_targets() {
452 Vec::new()
453 } else {
454 self.sub_agents.iter().map(|agent| agent.name().to_string()).collect()
455 };
456 let child_names: std::collections::HashSet<_> =
457 self.sub_agents.iter().map(|agent| agent.name()).collect();
458 let parent_name = ctx.run_config().parent_agent.as_deref();
459
460 for target in &ctx.run_config().transfer_targets {
461 if child_names.contains(target.as_str()) {
462 continue;
463 }
464 let is_parent = parent_name == Some(target.as_str());
465 if (is_parent && self.disallow_transfer_to_parent)
466 || (!is_parent && self.disallow_transfer_to_peers)
467 {
468 continue;
469 }
470 transfer_targets.push(target.clone());
471 }
472
473 if !transfer_targets.is_empty() {
474 declarations.insert(
475 "transfer_to_agent".to_string(),
476 serde_json::json!({
477 "name": "transfer_to_agent",
478 "description": format!(
479 "Transfer execution to another agent. Valid targets: {}",
480 transfer_targets.join(", ")
481 ),
482 "parameters": {
483 "type": "object",
484 "properties": {
485 "agent_name": {
486 "type": "string",
487 "description": "The name of the agent to transfer to.",
488 "enum": transfer_targets
489 }
490 },
491 "required": ["agent_name"]
492 }
493 }),
494 );
495 }
496
497 Ok(ResolvedTools { map, declarations, transfer_targets })
498 }
499
500 async fn process_llm_request(
502 &self,
503 ctx: &Arc<dyn InvocationContext>,
504 request: &mut LlmRequest,
505 ) -> Result<()> {
506 for toolset in self.toolsets.iter().map(AsRef::as_ref).chain(
507 ctx.run_config().runtime_toolsets.iter().map(|runtime| runtime.toolset().as_ref()),
508 ) {
509 toolset.process_llm_request(ctx.clone() as Arc<dyn ReadonlyContext>, request).await?;
510 }
511 Ok(())
512 }
513}
514
515impl std::fmt::Debug for LlmAgent {
516 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
517 f.debug_struct("LlmAgent")
518 .field("name", &self.name)
519 .field("description", &self.description)
520 .field("model", &self.model.name())
521 .field("instruction", &self.instruction)
522 .field("tools_count", &self.tools.len())
523 .field("sub_agents_count", &self.sub_agents.len())
524 .finish()
525 }
526}
527
528fn static_confirmation_decision(
536 decisions: &std::collections::HashMap<String, ToolConfirmationDecision>,
537 fingerprints: &std::collections::HashMap<String, String>,
538 function_call_id: &str,
539 tool_name: &str,
540 args: &serde_json::Value,
541) -> Option<ToolConfirmationDecision> {
542 let decision = decisions.get(function_call_id).copied()?;
543 if let Some(expected) = fingerprints.get(function_call_id) {
544 let actual = adk_core::tool_call_fingerprint(tool_name, args);
545 if &actual != expected {
546 tracing::warn!(
547 tool.name = %tool_name,
548 function_call.id = %function_call_id,
549 "confirmation decision does not match this call's arguments, treating as unconfirmed"
550 );
551 return None;
552 }
553 }
554 Some(decision)
555}
556
557impl LlmAgent {
558 #[cfg(feature = "sandbox")]
565 pub fn sandbox_config(&self) -> Option<&adk_sandbox::workspace::SandboxConfig> {
566 self.sandbox_config.as_ref()
567 }
568
569 async fn apply_input_guardrails(
570 ctx: Arc<dyn InvocationContext>,
571 input_guardrails: Arc<GuardrailSet>,
572 ) -> Result<Arc<dyn InvocationContext>> {
573 let content =
574 enforce_guardrails(input_guardrails.as_ref(), ctx.user_content(), "input").await?;
575 if content.role != ctx.user_content().role || content.parts != ctx.user_content().parts {
576 Ok(with_user_content_override(ctx, content))
577 } else {
578 Ok(ctx)
579 }
580 }
581
582 async fn apply_output_guardrails(
583 output_guardrails: &GuardrailSet,
584 content: Content,
585 ) -> Result<Content> {
586 enforce_guardrails(output_guardrails, &content, "output").await
587 }
588
589 fn history_parts_from_provider_metadata(
590 provider_metadata: Option<&serde_json::Value>,
591 ) -> Vec<Part> {
592 let Some(provider_metadata) = provider_metadata else {
593 return Vec::new();
594 };
595
596 let history_parts = provider_metadata
597 .get("conversation_history_parts")
598 .or_else(|| {
599 provider_metadata
600 .get("openai")
601 .and_then(|openai| openai.get("conversation_history_parts"))
602 })
603 .and_then(serde_json::Value::as_array);
604
605 history_parts
606 .into_iter()
607 .flatten()
608 .filter_map(|value| serde_json::from_value::<Part>(value.clone()).ok())
609 .collect()
610 }
611
612 fn continues_turn(metadata: Option<&serde_json::Value>) -> bool {
613 metadata
614 .and_then(|metadata| metadata.get("continue_turn"))
615 .and_then(serde_json::Value::as_bool)
616 == Some(true)
617 }
618
619 fn augment_content_for_history(
620 content: &Content,
621 provider_metadata: Option<&serde_json::Value>,
622 ) -> Content {
623 let mut augmented = content.clone();
624 augmented.parts.extend(Self::history_parts_from_provider_metadata(provider_metadata));
625 augmented
626 }
627}
628
629fn validate_output_against_schema(
634 text: &str,
635 schema: &serde_json::Value,
636) -> std::result::Result<serde_json::Value, String> {
637 let parsed: serde_json::Value =
638 serde_json::from_str(text).map_err(|e| format!("Response is not valid JSON: {e}"))?;
639
640 let validator =
641 jsonschema::validator_for(schema).map_err(|e| format!("Invalid schema: {e}"))?;
642
643 let errors: Vec<String> = validator.iter_errors(&parsed).map(|e| e.to_string()).collect();
644
645 if errors.is_empty() { Ok(parsed) } else { Err(errors.join("; ")) }
646}
647
648fn extract_text_from_events(events: &[Event]) -> Option<String> {
653 for event in events.iter().rev() {
654 if let Some(ref content) = event.llm_response.content {
655 let text: String =
656 content
657 .parts
658 .iter()
659 .filter_map(|p| {
660 if let Part::Text { text } = p { Some(text.as_str()) } else { None }
661 })
662 .collect::<Vec<_>>()
663 .join("");
664 if !text.is_empty() {
665 return Some(text);
666 }
667 }
668 }
669 None
670}
671
672pub fn extract_typed<T: serde::de::DeserializeOwned>(events: &[Event]) -> Result<T> {
694 let text = extract_text_from_events(events).ok_or_else(|| {
695 adk_core::AdkError::agent("no text content found in events for typed extraction")
696 })?;
697
698 serde_json::from_str(&text)
699 .map_err(|e| adk_core::AdkError::agent(format!("output deserialization failed: {e}")))
700}
701
702pub struct LlmAgentBuilder {
704 name: String,
705 description: Option<String>,
706 model: Option<Arc<dyn Llm>>,
707 instruction: Option<String>,
708 instruction_provider: Option<Arc<InstructionProvider>>,
709 global_instruction: Option<String>,
710 global_instruction_provider: Option<Arc<GlobalInstructionProvider>>,
711 skills_index: Option<Arc<SkillIndex>>,
712 skill_policy: SelectionPolicy,
713 max_skill_chars: usize,
714 input_schema: Option<serde_json::Value>,
715 output_schema: Option<serde_json::Value>,
716 output_max_retries: usize,
717 disallow_transfer_to_parent: bool,
718 disallow_transfer_to_peers: bool,
719 include_contents: adk_core::IncludeContents,
720 tools: Vec<Arc<dyn Tool>>,
721 toolsets: Vec<Arc<dyn Toolset>>,
722 sub_agents: Vec<Arc<dyn Agent>>,
723 output_key: Option<String>,
724 generate_content_config: Option<adk_core::GenerateContentConfig>,
725 max_iterations: u32,
726 tool_timeout: std::time::Duration,
727 before_callbacks: Vec<BeforeAgentCallback>,
728 after_callbacks: Vec<AfterAgentCallback>,
729 before_model_callbacks: Vec<BeforeModelCallback>,
730 after_model_callbacks: Vec<AfterModelCallback>,
731 before_tool_callbacks: Vec<BeforeToolCallback>,
732 after_tool_callbacks: Vec<AfterToolCallback>,
733 on_tool_error_callbacks: Vec<OnToolErrorCallback>,
734 after_tool_callbacks_full: Vec<AfterToolCallbackFull>,
735 default_retry_budget: Option<RetryBudget>,
736 tool_retry_budgets: std::collections::HashMap<String, RetryBudget>,
737 circuit_breaker_threshold: Option<u32>,
738 tool_confirmation_policy: ToolConfirmationPolicy,
739 tool_execution_strategy: Option<ToolExecutionStrategy>,
740 parallelize_agent_delegations: bool,
741 input_guardrails: GuardrailSet,
742 output_guardrails: GuardrailSet,
743 tool_guardrails: ToolGuardrailSet,
744 #[cfg(feature = "enhanced-plugins")]
746 enhanced_plugins: Vec<Arc<dyn EnhancedPlugin>>,
747 #[cfg(feature = "sandbox")]
749 sandbox_config: Option<adk_sandbox::workspace::SandboxConfig>,
750}
751
752impl LlmAgentBuilder {
753 pub fn new(name: impl Into<String>) -> Self {
755 Self {
756 name: name.into(),
757 description: None,
758 model: None,
759 instruction: None,
760 instruction_provider: None,
761 global_instruction: None,
762 global_instruction_provider: None,
763 skills_index: None,
764 skill_policy: SelectionPolicy::default(),
765 max_skill_chars: 2000,
766 input_schema: None,
767 output_schema: None,
768 output_max_retries: 3,
769 disallow_transfer_to_parent: false,
770 disallow_transfer_to_peers: false,
771 include_contents: adk_core::IncludeContents::Default,
772 tools: Vec::new(),
773 toolsets: Vec::new(),
774 sub_agents: Vec::new(),
775 output_key: None,
776 generate_content_config: None,
777 max_iterations: DEFAULT_MAX_ITERATIONS,
778 tool_timeout: DEFAULT_TOOL_TIMEOUT,
779 before_callbacks: Vec::new(),
780 after_callbacks: Vec::new(),
781 before_model_callbacks: Vec::new(),
782 after_model_callbacks: Vec::new(),
783 before_tool_callbacks: Vec::new(),
784 after_tool_callbacks: Vec::new(),
785 on_tool_error_callbacks: Vec::new(),
786 after_tool_callbacks_full: Vec::new(),
787 default_retry_budget: None,
788 tool_retry_budgets: std::collections::HashMap::new(),
789 circuit_breaker_threshold: None,
790 tool_confirmation_policy: ToolConfirmationPolicy::Never,
791 tool_execution_strategy: None,
792 parallelize_agent_delegations: false,
793 input_guardrails: GuardrailSet::new(),
794 output_guardrails: GuardrailSet::new(),
795 tool_guardrails: ToolGuardrailSet::new(),
796 #[cfg(feature = "enhanced-plugins")]
797 enhanced_plugins: Vec::new(),
798 #[cfg(feature = "sandbox")]
799 sandbox_config: None,
800 }
801 }
802
803 pub fn description(mut self, desc: impl Into<String>) -> Self {
805 self.description = Some(desc.into());
806 self
807 }
808
809 pub fn model(mut self, model: Arc<dyn Llm>) -> Self {
811 self.model = Some(model);
812 self
813 }
814
815 pub fn instruction(mut self, instruction: impl Into<String>) -> Self {
817 self.instruction = Some(instruction.into());
818 self
819 }
820
821 pub fn instruction_provider(mut self, provider: InstructionProvider) -> Self {
823 self.instruction_provider = Some(Arc::new(provider));
824 self
825 }
826
827 pub fn global_instruction(mut self, instruction: impl Into<String>) -> Self {
829 self.global_instruction = Some(instruction.into());
830 self
831 }
832
833 pub fn global_instruction_provider(mut self, provider: GlobalInstructionProvider) -> Self {
835 self.global_instruction_provider = Some(Arc::new(provider));
836 self
837 }
838
839 #[cfg(feature = "skills")]
844 pub fn with_skills(mut self, index: SkillIndex) -> Self {
845 self.skills_index = Some(Arc::new(index));
846 self
847 }
848
849 #[cfg(feature = "skills")]
851 pub fn with_auto_skills(self) -> Result<Self> {
852 self.with_skills_from_root(".")
853 }
854
855 #[cfg(feature = "skills")]
857 pub fn with_skills_from_root(mut self, root: impl AsRef<std::path::Path>) -> Result<Self> {
858 let index = load_skill_index(root).map_err(|e| adk_core::AdkError::agent(e.to_string()))?;
859 self.skills_index = Some(Arc::new(index));
860 Ok(self)
861 }
862
863 #[cfg(feature = "skills")]
865 pub fn with_skill_policy(mut self, policy: SelectionPolicy) -> Self {
866 self.skill_policy = policy;
867 self
868 }
869
870 #[cfg(feature = "skills")]
872 pub fn with_skill_budget(mut self, max_chars: usize) -> Self {
873 self.max_skill_chars = max_chars;
874 self
875 }
876
877 pub fn input_schema(mut self, schema: serde_json::Value) -> Self {
879 self.input_schema = Some(schema);
880 self
881 }
882
883 pub fn output_schema(mut self, schema: serde_json::Value) -> Self {
885 self.output_schema = Some(schema);
886 self
887 }
888
889 pub fn output_type<T: schemars::JsonSchema>(mut self) -> Self {
912 let schema = schemars::schema_for!(T);
913 self.output_schema =
914 Some(serde_json::to_value(schema).expect("schema serialization cannot fail"));
915 self
916 }
917
918 pub fn output_max_retries(mut self, n: usize) -> Self {
923 self.output_max_retries = n;
924 self
925 }
926
927 pub fn disallow_transfer_to_parent(mut self, disallow: bool) -> Self {
929 self.disallow_transfer_to_parent = disallow;
930 self
931 }
932
933 pub fn disallow_transfer_to_peers(mut self, disallow: bool) -> Self {
935 self.disallow_transfer_to_peers = disallow;
936 self
937 }
938
939 pub fn include_contents(mut self, include: adk_core::IncludeContents) -> Self {
941 self.include_contents = include;
942 self
943 }
944
945 pub fn output_key(mut self, key: impl Into<String>) -> Self {
951 self.output_key = Some(key.into());
952 self
953 }
954
955 pub fn generate_content_config(mut self, config: adk_core::GenerateContentConfig) -> Self {
976 self.generate_content_config = Some(config);
977 self
978 }
979
980 pub fn temperature(mut self, temperature: f32) -> Self {
983 self.generate_content_config
984 .get_or_insert(adk_core::GenerateContentConfig::default())
985 .temperature = Some(temperature);
986 self
987 }
988
989 pub fn top_p(mut self, top_p: f32) -> Self {
991 self.generate_content_config
992 .get_or_insert(adk_core::GenerateContentConfig::default())
993 .top_p = Some(top_p);
994 self
995 }
996
997 pub fn top_k(mut self, top_k: i32) -> Self {
999 self.generate_content_config
1000 .get_or_insert(adk_core::GenerateContentConfig::default())
1001 .top_k = Some(top_k);
1002 self
1003 }
1004
1005 pub fn max_output_tokens(mut self, max_tokens: i32) -> Self {
1007 self.generate_content_config
1008 .get_or_insert(adk_core::GenerateContentConfig::default())
1009 .max_output_tokens = Some(max_tokens);
1010 self
1011 }
1012
1013 pub fn max_iterations(mut self, max: u32) -> Self {
1016 self.max_iterations = max;
1017 self
1018 }
1019
1020 pub fn tool_timeout(mut self, timeout: std::time::Duration) -> Self {
1023 self.tool_timeout = timeout;
1024 self
1025 }
1026
1027 pub fn tool(mut self, tool: Arc<dyn Tool>) -> Self {
1029 self.tools.push(tool);
1030 self
1031 }
1032
1033 pub fn toolset(mut self, toolset: Arc<dyn Toolset>) -> Self {
1039 self.toolsets.push(toolset);
1040 self
1041 }
1042
1043 pub fn sub_agent(mut self, agent: Arc<dyn Agent>) -> Self {
1045 self.sub_agents.push(agent);
1046 self
1047 }
1048
1049 pub fn before_callback(mut self, callback: BeforeAgentCallback) -> Self {
1051 self.before_callbacks.push(callback);
1052 self
1053 }
1054
1055 pub fn after_callback(mut self, callback: AfterAgentCallback) -> Self {
1057 self.after_callbacks.push(callback);
1058 self
1059 }
1060
1061 pub fn before_model_callback(mut self, callback: BeforeModelCallback) -> Self {
1063 self.before_model_callbacks.push(callback);
1064 self
1065 }
1066
1067 pub fn after_model_callback(mut self, callback: AfterModelCallback) -> Self {
1069 self.after_model_callbacks.push(callback);
1070 self
1071 }
1072
1073 pub fn before_tool_callback(mut self, callback: BeforeToolCallback) -> Self {
1075 self.before_tool_callbacks.push(callback);
1076 self
1077 }
1078
1079 pub fn after_tool_callback(mut self, callback: AfterToolCallback) -> Self {
1081 self.after_tool_callbacks.push(callback);
1082 self
1083 }
1084
1085 pub fn after_tool_callback_full(mut self, callback: AfterToolCallbackFull) -> Self {
1100 self.after_tool_callbacks_full.push(callback);
1101 self
1102 }
1103
1104 pub fn on_tool_error(mut self, callback: OnToolErrorCallback) -> Self {
1112 self.on_tool_error_callbacks.push(callback);
1113 self
1114 }
1115
1116 pub fn default_retry_budget(mut self, budget: RetryBudget) -> Self {
1123 self.default_retry_budget = Some(budget);
1124 self
1125 }
1126
1127 pub fn tool_retry_budget(mut self, tool_name: impl Into<String>, budget: RetryBudget) -> Self {
1132 self.tool_retry_budgets.insert(tool_name.into(), budget);
1133 self
1134 }
1135
1136 pub fn circuit_breaker_threshold(mut self, threshold: u32) -> Self {
1143 self.circuit_breaker_threshold = Some(threshold);
1144 self
1145 }
1146
1147 pub fn tool_confirmation_policy(mut self, policy: ToolConfirmationPolicy) -> Self {
1155 self.tool_confirmation_policy = policy;
1156 self
1157 }
1158
1159 pub fn require_tool_confirmation(mut self, tool_name: impl Into<String>) -> Self {
1161 self.tool_confirmation_policy = self.tool_confirmation_policy.with_tool(tool_name);
1162 self
1163 }
1164
1165 pub fn require_tool_confirmation_for_all(mut self) -> Self {
1167 self.tool_confirmation_policy = ToolConfirmationPolicy::Always;
1168 self
1169 }
1170
1171 pub fn tool_execution_strategy(mut self, strategy: ToolExecutionStrategy) -> Self {
1179 self.tool_execution_strategy = Some(strategy);
1180 self
1181 }
1182
1183 pub fn parallelize_agent_delegations(mut self, parallelize: bool) -> Self {
1189 self.parallelize_agent_delegations = parallelize;
1190 self
1191 }
1192
1193 pub fn input_guardrails(mut self, guardrails: GuardrailSet) -> Self {
1202 self.input_guardrails = guardrails;
1203 self
1204 }
1205
1206 pub fn output_guardrails(mut self, guardrails: GuardrailSet) -> Self {
1215 self.output_guardrails = guardrails;
1216 self
1217 }
1218
1219 pub fn tool_guardrails(mut self, guardrails: ToolGuardrailSet) -> Self {
1245 self.tool_guardrails = guardrails;
1246 self
1247 }
1248
1249 #[cfg(feature = "enhanced-plugins")]
1269 pub fn enhanced_plugin(mut self, plugin: Arc<dyn EnhancedPlugin>) -> Self {
1270 self.enhanced_plugins.push(plugin);
1271 self
1272 }
1273
1274 #[cfg(feature = "enhanced-plugins")]
1296 pub fn enhanced_plugins(mut self, plugins: Vec<Arc<dyn EnhancedPlugin>>) -> Self {
1297 self.enhanced_plugins.extend(plugins);
1298 self
1299 }
1300
1301 #[cfg(feature = "sandbox")]
1336 pub fn sandbox_config(mut self, config: adk_sandbox::workspace::SandboxConfig) -> Self {
1337 self.sandbox_config = Some(config);
1338 self
1339 }
1340
1341 pub fn build(self) -> Result<LlmAgent> {
1343 let model = self.model.ok_or_else(|| adk_core::AdkError::agent("Model is required"))?;
1344
1345 let mut seen_names = std::collections::HashSet::new();
1346 for agent in &self.sub_agents {
1347 if !seen_names.insert(agent.name()) {
1348 return Err(adk_core::AdkError::agent(format!(
1349 "Duplicate sub-agent name: {}",
1350 agent.name()
1351 )));
1352 }
1353 }
1354
1355 #[cfg(feature = "sandbox")]
1358 if let Some(ref sandbox_cfg) = self.sandbox_config {
1359 use adk_sandbox::workspace::Capability;
1360 if model.uses_interactions_api()
1361 && (sandbox_cfg.capabilities.contains(&Capability::Shell)
1362 || sandbox_cfg.capabilities.contains(&Capability::Filesystem))
1363 {
1364 return Err(adk_core::AdkError::new(
1365 adk_core::ErrorComponent::Agent,
1366 adk_core::ErrorCategory::InvalidInput,
1367 "code.gemini_interactions_conflict",
1368 "Cannot combine Gemini Interactions API (server-managed environment) \
1369 with client-side sandbox tools (Shell/Filesystem). These provide \
1370 competing filesystems and would produce nondeterministic behavior. \
1371 Either disable use_interactions_api or remove sandbox capabilities.",
1372 ));
1373 }
1374 }
1375
1376 #[cfg(feature = "enhanced-plugins")]
1378 let enhanced_plugin_manager = if self.enhanced_plugins.is_empty() {
1379 None
1380 } else {
1381 Some(Arc::new(EnhancedPluginManager::new(self.enhanced_plugins)))
1382 };
1383
1384 Ok(LlmAgent {
1385 name: self.name,
1386 description: self.description.unwrap_or_default(),
1387 model,
1388 instruction: self.instruction,
1389 instruction_provider: self.instruction_provider,
1390 global_instruction: self.global_instruction,
1391 global_instruction_provider: self.global_instruction_provider,
1392 skills_index: self.skills_index,
1393 skill_policy: self.skill_policy,
1394 max_skill_chars: self.max_skill_chars,
1395 input_schema: self.input_schema,
1396 output_schema: self.output_schema,
1397 output_max_retries: self.output_max_retries,
1398 disallow_transfer_to_parent: self.disallow_transfer_to_parent,
1399 disallow_transfer_to_peers: self.disallow_transfer_to_peers,
1400 include_contents: self.include_contents,
1401 tools: self.tools,
1402 toolsets: self.toolsets,
1403 sub_agents: self.sub_agents,
1404 output_key: self.output_key,
1405 generate_content_config: self.generate_content_config,
1406 max_iterations: self.max_iterations,
1407 tool_timeout: self.tool_timeout,
1408 before_callbacks: Arc::new(self.before_callbacks),
1409 after_callbacks: Arc::new(self.after_callbacks),
1410 before_model_callbacks: Arc::new(self.before_model_callbacks),
1411 after_model_callbacks: Arc::new(self.after_model_callbacks),
1412 before_tool_callbacks: Arc::new(self.before_tool_callbacks),
1413 after_tool_callbacks: Arc::new(self.after_tool_callbacks),
1414 on_tool_error_callbacks: Arc::new(self.on_tool_error_callbacks),
1415 after_tool_callbacks_full: Arc::new(self.after_tool_callbacks_full),
1416 default_retry_budget: self.default_retry_budget,
1417 tool_retry_budgets: self.tool_retry_budgets,
1418 circuit_breaker_threshold: self.circuit_breaker_threshold,
1419 tool_confirmation_policy: self.tool_confirmation_policy,
1420 tool_execution_strategy: self.tool_execution_strategy,
1421 parallelize_agent_delegations: self.parallelize_agent_delegations,
1422 input_guardrails: Arc::new(self.input_guardrails),
1423 output_guardrails: Arc::new(self.output_guardrails),
1424 tool_guardrails: Arc::new(self.tool_guardrails),
1425 #[cfg(feature = "enhanced-plugins")]
1426 enhanced_plugin_manager,
1427 #[cfg(feature = "sandbox")]
1428 sandbox_config: self.sandbox_config,
1429 })
1430 }
1431}
1432
1433const TOOL_PROGRESS_CAPACITY: usize = 256;
1441
1442const TOOL_PROGRESS_SEND_TIMEOUT: std::time::Duration = std::time::Duration::from_millis(100);
1447
1448const TOOL_PROGRESS_MAX_CHUNK_BYTES: usize = 8 * 1024;
1450
1451const TOOL_PROGRESS_MAX_TOTAL_BYTES: usize = 1024 * 1024;
1453
1454const TOOL_PROGRESS_TRUNCATION_MARKER: &str = "[adk: tool progress truncated]";
1456
1457fn truncate_on_char_boundary(text: &str, max_bytes: usize) -> &str {
1459 if text.len() <= max_bytes {
1460 return text;
1461 }
1462 let mut end = max_bytes;
1463 while end > 0 && !text.is_char_boundary(end) {
1464 end -= 1;
1465 }
1466 &text[..end]
1467}
1468
1469struct AgentToolContext {
1470 parent_ctx: Arc<dyn InvocationContext>,
1471 function_call_id: String,
1472 tool_name: Option<String>,
1475 actions: Mutex<EventActions>,
1476 progress_tx: Option<tokio::sync::mpsc::Sender<Event>>,
1477 progress_bytes: std::sync::atomic::AtomicUsize,
1479 progress_truncated: std::sync::atomic::AtomicBool,
1481}
1482
1483impl AgentToolContext {
1484 fn new(parent_ctx: Arc<dyn InvocationContext>, function_call_id: String) -> Self {
1485 Self {
1486 parent_ctx,
1487 function_call_id,
1488 tool_name: None,
1489 actions: Mutex::new(EventActions::default()),
1490 progress_tx: None,
1491 progress_bytes: std::sync::atomic::AtomicUsize::new(0),
1492 progress_truncated: std::sync::atomic::AtomicBool::new(false),
1493 }
1494 }
1495
1496 fn with_tool_name(mut self, tool_name: impl Into<String>) -> Self {
1498 self.tool_name = Some(tool_name.into());
1499 self
1500 }
1501
1502 fn with_progress(mut self, tx: tokio::sync::mpsc::Sender<Event>) -> Self {
1505 self.progress_tx = Some(tx);
1506 self
1507 }
1508
1509 async fn request_secret(&self, name: &str, purpose: Option<&str>) -> Result<Option<String>> {
1514 let mut request = adk_core::SecretRequest::new(name)
1515 .with_identity(
1516 self.parent_ctx.app_name(),
1517 self.parent_ctx.user_id(),
1518 self.parent_ctx.session_id(),
1519 )
1520 .with_invocation_id(self.parent_ctx.invocation_id());
1521 if let Some(tool_name) = &self.tool_name {
1522 request = request.with_tool_name(tool_name);
1523 }
1524 if let Some(purpose) = purpose {
1525 request = request.with_purpose(purpose);
1526 }
1527 self.parent_ctx.get_secret_for(&request).await
1528 }
1529
1530 async fn forward_progress(
1537 &self,
1538 tx: &tokio::sync::mpsc::Sender<Event>,
1539 stream: &str,
1540 chunk: &str,
1541 ) {
1542 use std::sync::atomic::Ordering;
1543
1544 if self.progress_truncated.load(Ordering::Relaxed) {
1545 return;
1546 }
1547
1548 let payload = truncate_on_char_boundary(chunk, TOOL_PROGRESS_MAX_CHUNK_BYTES);
1551 let forwarded = self.progress_bytes.fetch_add(payload.len(), Ordering::Relaxed);
1552 if forwarded.saturating_add(payload.len()) > TOOL_PROGRESS_MAX_TOTAL_BYTES {
1553 self.mark_progress_truncated(tx, stream).await;
1554 return;
1555 }
1556
1557 let event = Event::tool_progress(
1558 self.parent_ctx.invocation_id(),
1559 self.parent_ctx.agent_name(),
1560 &self.function_call_id,
1561 stream,
1562 payload,
1563 );
1564
1565 match tx.try_send(event) {
1566 Ok(()) => {}
1567 Err(tokio::sync::mpsc::error::TrySendError::Full(event)) => {
1568 match tokio::time::timeout(TOOL_PROGRESS_SEND_TIMEOUT, tx.send(event)).await {
1571 Ok(Ok(())) => {}
1572 Ok(Err(_)) => {}
1573 Err(_) => self.mark_progress_truncated(tx, stream).await,
1574 }
1575 }
1576 Err(tokio::sync::mpsc::error::TrySendError::Closed(_)) => {}
1577 }
1578 }
1579
1580 async fn mark_progress_truncated(&self, tx: &tokio::sync::mpsc::Sender<Event>, stream: &str) {
1582 use std::sync::atomic::Ordering;
1583 if self.progress_truncated.swap(true, Ordering::Relaxed) {
1584 return;
1585 }
1586 tracing::warn!(
1587 function_call.id = %self.function_call_id,
1588 progress.stream = %stream,
1589 "tool progress exceeded its budget, remaining output is not forwarded"
1590 );
1591 let marker = Event::tool_progress(
1592 self.parent_ctx.invocation_id(),
1593 self.parent_ctx.agent_name(),
1594 &self.function_call_id,
1595 stream,
1596 TOOL_PROGRESS_TRUNCATION_MARKER,
1597 );
1598 let _ = tx.try_send(marker);
1599 }
1600
1601 fn actions_guard(&self) -> std::sync::MutexGuard<'_, EventActions> {
1602 self.actions.lock().unwrap_or_else(|e| e.into_inner())
1603 }
1604}
1605
1606#[async_trait]
1607impl ReadonlyContext for AgentToolContext {
1608 fn invocation_id(&self) -> &str {
1609 self.parent_ctx.invocation_id()
1610 }
1611
1612 fn agent_name(&self) -> &str {
1613 self.parent_ctx.agent_name()
1614 }
1615
1616 fn user_id(&self) -> &str {
1617 self.parent_ctx.user_id()
1619 }
1620
1621 fn app_name(&self) -> &str {
1622 self.parent_ctx.app_name()
1624 }
1625
1626 fn session_id(&self) -> &str {
1627 self.parent_ctx.session_id()
1629 }
1630
1631 fn branch(&self) -> &str {
1632 self.parent_ctx.branch()
1633 }
1634
1635 fn user_content(&self) -> &Content {
1636 self.parent_ctx.user_content()
1637 }
1638}
1639
1640#[async_trait]
1641impl CallbackContext for AgentToolContext {
1642 fn artifacts(&self) -> Option<Arc<dyn adk_core::Artifacts>> {
1643 self.parent_ctx.artifacts()
1645 }
1646
1647 fn shared_state(&self) -> Option<Arc<adk_core::SharedState>> {
1648 self.parent_ctx.shared_state()
1649 }
1650}
1651
1652#[async_trait]
1653impl ToolContext for AgentToolContext {
1654 fn function_call_id(&self) -> &str {
1655 &self.function_call_id
1656 }
1657
1658 fn actions(&self) -> EventActions {
1659 self.actions_guard().clone()
1660 }
1661
1662 fn set_actions(&self, actions: EventActions) {
1663 *self.actions_guard() = actions;
1664 }
1665
1666 async fn search_memory(&self, query: &str) -> Result<Vec<MemoryEntry>> {
1667 if let Some(memory) = self.parent_ctx.memory() {
1669 memory.search(query).await
1670 } else {
1671 Ok(vec![])
1672 }
1673 }
1674
1675 fn memory(&self) -> Option<Arc<dyn adk_core::Memory>> {
1676 self.parent_ctx.memory()
1677 }
1678
1679 fn session(&self) -> Option<&dyn adk_core::Session> {
1680 Some(self.parent_ctx.session())
1681 }
1682
1683 fn run_config(&self) -> Option<&adk_core::RunConfig> {
1684 Some(self.parent_ctx.run_config())
1685 }
1686
1687 fn is_cancelled(&self) -> bool {
1688 self.parent_ctx.is_cancelled()
1689 }
1690
1691 fn request_metadata(&self) -> HashMap<String, serde_json::Value> {
1692 self.parent_ctx.request_metadata()
1693 }
1694
1695 fn delegation_depth(&self) -> u32 {
1696 self.parent_ctx.delegation_depth()
1697 }
1698
1699 fn max_delegation_depth(&self) -> Option<u32> {
1700 self.parent_ctx.max_delegation_depth()
1701 }
1702
1703 fn orchestration_root_invocation_id(&self) -> &str {
1704 self.parent_ctx.orchestration_root_invocation_id()
1705 }
1706
1707 fn orchestration_edge_id(&self) -> Option<&str> {
1708 self.parent_ctx.orchestration_edge_id()
1709 }
1710
1711 async fn emit_event(&self, event: Event) {
1712 if let Some(tx) = &self.progress_tx
1713 && !tx.is_closed()
1714 {
1715 let _ = tokio::time::timeout(TOOL_PROGRESS_SEND_TIMEOUT, tx.send(event)).await;
1716 }
1717 }
1718
1719 fn user_scopes(&self) -> Vec<String> {
1720 self.parent_ctx.user_scopes()
1721 }
1722
1723 async fn get_secret(&self, name: &str) -> Result<Option<String>> {
1724 self.request_secret(name, None).await
1725 }
1726
1727 async fn get_secret_for_purpose(&self, name: &str, purpose: &str) -> Result<Option<String>> {
1728 self.request_secret(name, Some(purpose)).await
1729 }
1730
1731 async fn emit_progress(&self, stream: &str, chunk: &str) {
1732 if let Some(tx) = &self.progress_tx {
1735 if !tx.is_closed() {
1737 self.forward_progress(tx, stream, chunk).await;
1738 }
1739 }
1740 tracing::debug!(
1742 target: "adk_agent::tool_progress",
1743 tool_call_id = %self.function_call_id,
1744 stream = %stream,
1745 "{chunk}",
1746 );
1747 }
1748}
1749
1750struct ToolOutcomeCallbackContext {
1754 inner: Arc<dyn CallbackContext>,
1755 outcome: ToolOutcome,
1756}
1757
1758#[async_trait]
1759impl ReadonlyContext for ToolOutcomeCallbackContext {
1760 fn invocation_id(&self) -> &str {
1761 self.inner.invocation_id()
1762 }
1763
1764 fn agent_name(&self) -> &str {
1765 self.inner.agent_name()
1766 }
1767
1768 fn user_id(&self) -> &str {
1769 self.inner.user_id()
1770 }
1771
1772 fn app_name(&self) -> &str {
1773 self.inner.app_name()
1774 }
1775
1776 fn session_id(&self) -> &str {
1777 self.inner.session_id()
1778 }
1779
1780 fn branch(&self) -> &str {
1781 self.inner.branch()
1782 }
1783
1784 fn user_content(&self) -> &Content {
1785 self.inner.user_content()
1786 }
1787}
1788
1789#[async_trait]
1790impl CallbackContext for ToolOutcomeCallbackContext {
1791 fn artifacts(&self) -> Option<Arc<dyn adk_core::Artifacts>> {
1792 self.inner.artifacts()
1793 }
1794
1795 fn tool_outcome(&self) -> Option<ToolOutcome> {
1796 Some(self.outcome.clone())
1797 }
1798
1799 fn shared_state(&self) -> Option<Arc<adk_core::SharedState>> {
1800 self.inner.shared_state()
1801 }
1802}
1803
1804struct CircuitBreakerState {
1814 threshold: u32,
1815 failures: std::collections::HashMap<String, u32>,
1817}
1818
1819impl CircuitBreakerState {
1820 fn new(threshold: u32) -> Self {
1821 Self { threshold, failures: std::collections::HashMap::new() }
1822 }
1823
1824 fn is_open(&self, tool_name: &str) -> bool {
1826 self.failures.get(tool_name).copied().unwrap_or(0) >= self.threshold
1827 }
1828
1829 fn record(&mut self, outcome: &ToolOutcome) {
1831 if outcome.success {
1832 self.failures.remove(&outcome.tool_name);
1833 } else {
1834 let count = self.failures.entry(outcome.tool_name.clone()).or_insert(0);
1835 *count += 1;
1836 }
1837 }
1838}
1839
1840struct ToolExecutionResult {
1841 index: usize,
1842 content: Content,
1843 actions: EventActions,
1844 escalate_or_skip: bool,
1845}
1846
1847#[derive(Clone, Copy)]
1848enum ToolDispatchMode {
1849 Sequential,
1850 Parallel,
1851 ParallelDelegations,
1852 Auto,
1853}
1854
1855impl ToolDispatchMode {
1856 fn resolve(strategy: ToolExecutionStrategy, parallelize_agent_delegations: bool) -> Self {
1857 if parallelize_agent_delegations {
1858 return Self::ParallelDelegations;
1859 }
1860 match strategy {
1861 ToolExecutionStrategy::Sequential => Self::Sequential,
1862 ToolExecutionStrategy::Parallel => Self::Parallel,
1863 ToolExecutionStrategy::Auto => Self::Auto,
1864 }
1865 }
1866}
1867
1868struct ToolExecutor<'a> {
1869 ctx: Arc<dyn InvocationContext>,
1870 tool_map: &'a std::collections::HashMap<String, Arc<dyn Tool>>,
1871 tool_retry_budgets: &'a std::collections::HashMap<String, RetryBudget>,
1872 default_retry_budget: &'a Option<RetryBudget>,
1873 before_tool_callbacks: &'a Arc<Vec<BeforeToolCallback>>,
1874 after_tool_callbacks: &'a Arc<Vec<AfterToolCallback>>,
1875 after_tool_callbacks_full: &'a Arc<Vec<AfterToolCallbackFull>>,
1876 on_tool_error_callbacks: &'a Arc<Vec<OnToolErrorCallback>>,
1877 tool_confirmation_policy: &'a ToolConfirmationPolicy,
1878 cb_mutex: &'a std::sync::Mutex<Option<CircuitBreakerState>>,
1879 invocation_id: &'a str,
1880 concurrency_manager: &'a adk_core::ToolConcurrencyManager,
1881 progress_tx: tokio::sync::mpsc::Sender<Event>,
1882 tool_timeout: std::time::Duration,
1883 confirmation_decisions: &'a std::collections::HashMap<String, ToolConfirmationDecision>,
1884 confirmation_fingerprints: &'a std::collections::HashMap<String, String>,
1885 confirmation_lock: tokio::sync::Mutex<()>,
1888 approval_failed: tokio::sync::watch::Sender<bool>,
1890 #[cfg(feature = "enhanced-plugins")]
1891 enhanced_plugin_manager: &'a Option<Arc<EnhancedPluginManager>>,
1892}
1893
1894impl ToolExecutor<'_> {
1895 async fn execute(&self, call: PendingToolCall) -> Result<ToolExecutionResult> {
1896 let result = self.execute_inner(call).await?;
1897 let mut event = Event::new(self.invocation_id);
1898 event.author = self.ctx.agent_name().to_owned();
1899 event.branch = self.ctx.branch().to_owned();
1900 event.actions = result.actions.clone();
1901 event.llm_response.content = Some(result.content.clone());
1902 let _ = self.progress_tx.send(event).await;
1904 Ok(result)
1905 }
1906
1907 async fn execute_inner(&self, call: PendingToolCall) -> Result<ToolExecutionResult> {
1908 let PendingToolCall { index, name, args, id, function_call_id, guardrail_denial } = call;
1909 let mut tool_actions = EventActions::default();
1910 let mut response_content: Option<Content> = None;
1911 let mut run_after_tool_callbacks = true;
1912 let mut tool_outcome_for_callback: Option<ToolOutcome> = None;
1913 let mut executed_tool: Option<Arc<dyn Tool>> = None;
1914 let mut executed_tool_response: Option<serde_json::Value> = None;
1915
1916 if let Some(reason) = guardrail_denial {
1917 let denied_content = Content {
1920 role: "function".to_string(),
1921 parts: vec![Part::FunctionResponse {
1922 function_response: FunctionResponseData::new(
1923 name.clone(),
1924 serde_json::json!({ "error": reason }),
1925 ),
1926 id: id.clone(),
1927 annotations: None,
1928 }],
1929 };
1930 return Ok(ToolExecutionResult {
1931 index,
1932 content: denied_content,
1933 actions: tool_actions,
1934 escalate_or_skip: false,
1935 });
1936 }
1937
1938 if self.tool_confirmation_policy.requires_confirmation(&name)
1941 || self.ctx.requires_tool_confirmation(&name)
1942 {
1943 let mut decision = static_confirmation_decision(
1944 self.confirmation_decisions,
1945 self.confirmation_fingerprints,
1946 &function_call_id,
1947 &name,
1948 &args,
1949 );
1950 if decision.is_none()
1951 && let Some(handler) = self.ctx.run_config().tool_confirmation_handler.as_ref()
1952 {
1953 let request = ToolConfirmationRequest {
1954 tool_name: name.clone(),
1955 function_call_id: Some(function_call_id.clone()),
1956 args: args.clone(),
1957 };
1958 let mut approval_failed = self.approval_failed.subscribe();
1959 let decided = tokio::select! {
1961 biased;
1962 _ = approval_failed.wait_for(|failed| *failed) => {
1963 Err(adk_core::AdkError::tool(format!(
1964 "approval for tool '{name}' was abandoned because another approval in the same batch failed"
1965 )))
1966 }
1967 decided = async {
1968 let _prompt = self.confirmation_lock.lock().await;
1969 handler.decide(&request).await
1970 } => decided,
1971 };
1972 match decided {
1973 Ok(value) => decision = Some(value),
1974 Err(error) => {
1975 self.approval_failed.send_replace(true);
1976 return Err(error);
1977 }
1978 }
1979 }
1980 match decision {
1981 Some(ToolConfirmationDecision::Approve) => {
1982 tool_actions.tool_confirmation_decision =
1983 Some(ToolConfirmationDecision::Approve);
1984 }
1985 Some(ToolConfirmationDecision::Deny) => {
1986 tool_actions.tool_confirmation_decision = Some(ToolConfirmationDecision::Deny);
1987 response_content = Some(Content {
1988 role: "function".to_string(),
1989 parts: vec![Part::FunctionResponse {
1990 function_response: FunctionResponseData::new(
1991 name.clone(),
1992 serde_json::json!({
1993 "error": format!("Tool '{}' execution denied by confirmation policy", name)
1994 }),
1995 ),
1996 id: id.clone(),
1997 annotations: None,
1998 }],
1999 });
2000 run_after_tool_callbacks = false;
2001 }
2002 None => {
2003 response_content = Some(Content {
2004 role: "function".to_string(),
2005 parts: vec![Part::FunctionResponse {
2006 function_response: FunctionResponseData::new(
2007 name.clone(),
2008 serde_json::json!({
2009 "error": format!("Tool '{}' requires confirmation", name)
2010 }),
2011 ),
2012 id: id.clone(),
2013 annotations: None,
2014 }],
2015 });
2016 run_after_tool_callbacks = false;
2017 }
2018 }
2019 }
2020
2021 let _concurrency_permit = if response_content.is_some() {
2026 None
2027 } else {
2028 match self.concurrency_manager.acquire(&name).await {
2029 Ok(permit) => Some(permit),
2030 Err(e) => {
2031 let error_content = Content {
2033 role: "function".to_string(),
2034 parts: vec![Part::FunctionResponse {
2035 function_response: FunctionResponseData::new(
2036 name.clone(),
2037 serde_json::json!({ "error": e.to_string() }),
2038 ),
2039 id: id.clone(),
2040 annotations: None,
2041 }],
2042 };
2043 return Ok(ToolExecutionResult {
2044 index,
2045 content: error_content,
2046 actions: tool_actions,
2047 escalate_or_skip: false,
2048 });
2049 }
2050 }
2051 };
2052
2053 #[allow(unused_mut)]
2056 let mut final_args = args.clone();
2057
2058 #[cfg(feature = "enhanced-plugins")]
2060 if response_content.is_none()
2061 && let Some(epm) = self.enhanced_plugin_manager.as_ref()
2062 && let Some(tool_ref) = self.tool_map.get(&name)
2063 {
2064 match epm
2065 .run_before_tool_call(
2066 tool_ref.clone(),
2067 final_args.clone(),
2068 self.ctx.clone() as Arc<dyn CallbackContext>,
2069 )
2070 .await
2071 {
2072 Ok(BeforeToolCallResult::Continue(modified_args)) => {
2073 final_args = modified_args;
2074 }
2075 Ok(BeforeToolCallResult::ShortCircuit(synthetic_result)) => {
2076 response_content = Some(Content {
2078 role: "function".to_string(),
2079 parts: vec![Part::FunctionResponse {
2080 function_response: FunctionResponseData::from_tool_result(
2081 name.clone(),
2082 synthetic_result,
2083 ),
2084 id: id.clone(),
2085 annotations: None,
2086 }],
2087 });
2088 executed_tool = Some(tool_ref.clone());
2089 }
2090 Err(e) => {
2091 response_content = Some(Content {
2092 role: "function".to_string(),
2093 parts: vec![Part::FunctionResponse {
2094 function_response: FunctionResponseData::new(
2095 name.clone(),
2096 serde_json::json!({ "error": e.to_string() }),
2097 ),
2098 id: id.clone(),
2099 annotations: None,
2100 }],
2101 });
2102 run_after_tool_callbacks = false;
2103 }
2104 }
2105 }
2106
2107 if response_content.is_none() {
2108 let tool_ctx = Arc::new(ToolCallbackContext::new(
2109 self.ctx.clone(),
2110 name.clone(),
2111 final_args.clone(),
2112 ));
2113 for callback in self.before_tool_callbacks.as_ref() {
2114 match callback(tool_ctx.clone() as Arc<dyn CallbackContext>).await {
2115 Ok(Some(c)) => {
2116 response_content = Some(c);
2117 break;
2118 }
2119 Ok(None) => continue,
2120 Err(e) => {
2121 response_content = Some(Content {
2122 role: "function".to_string(),
2123 parts: vec![Part::FunctionResponse {
2124 function_response: FunctionResponseData::new(
2125 name.clone(),
2126 serde_json::json!({ "error": e.to_string() }),
2127 ),
2128 id: id.clone(),
2129 annotations: None,
2130 }],
2131 });
2132 run_after_tool_callbacks = false;
2133 break;
2134 }
2135 }
2136 }
2137 }
2138
2139 if response_content.is_none() {
2141 let guard = self.cb_mutex.lock().unwrap_or_else(|e| e.into_inner());
2142 if let Some(ref cb_state) = *guard
2143 && cb_state.is_open(&name)
2144 {
2145 let msg = format!(
2146 "Tool '{}' is temporarily disabled after {} consecutive failures",
2147 name, cb_state.threshold
2148 );
2149 tracing::warn!(tool.name = %name, "circuit breaker open, skipping tool execution");
2150 response_content = Some(Content {
2151 role: "function".to_string(),
2152 parts: vec![Part::FunctionResponse {
2153 function_response: FunctionResponseData::new(
2154 name.clone(),
2155 serde_json::json!({ "error": msg }),
2156 ),
2157 id: id.clone(),
2158 annotations: None,
2159 }],
2160 });
2161 run_after_tool_callbacks = false;
2162 }
2163 drop(guard);
2164 }
2165
2166 if response_content.is_none() {
2168 if let Some(tool) = self.tool_map.get(&name) {
2169 let tool_ctx: Arc<dyn ToolContext> = Arc::new(
2170 AgentToolContext::new(self.ctx.clone(), function_call_id.clone())
2171 .with_tool_name(tool.name())
2172 .with_progress(self.progress_tx.clone()),
2173 );
2174 let span_name = format!("execute_tool {name}");
2175 let tool_span = tracing::info_span!(
2176 "",
2177 otel.name = %span_name,
2178 tool.name = %name,
2179 "gcp.vertex.agent.event_id" = %format!("{}_{}", self.invocation_id, name),
2180 "gcp.vertex.agent.invocation_id" = %self.invocation_id,
2181 "gcp.vertex.agent.session_id" = %self.ctx.session_id(),
2182 "gen_ai.conversation.id" = %self.ctx.session_id()
2183 );
2184
2185 let budget =
2186 self.tool_retry_budgets.get(&name).or(self.default_retry_budget.as_ref());
2187 let max_attempts = budget.map(|b| b.max_retries + 1).unwrap_or(1);
2188 let retry_delay = budget.map(|b| b.delay).unwrap_or_default();
2189
2190 let mut started = Event::new(self.invocation_id);
2191 started.author = self.ctx.agent_name().to_owned();
2192 started.branch = self.ctx.branch().to_owned();
2193 started.llm_response.partial = true;
2195 started.provider_metadata.insert(
2196 "adk_tool_started".into(),
2197 serde_json::json!({"id": id, "name": name}).to_string(),
2198 );
2199 let _ = self.progress_tx.send(started).await;
2200 let tool_clone = tool.clone();
2201 let tool_start = std::time::Instant::now();
2202 let mut last_error = String::new();
2203 let mut final_attempt: u32 = 0;
2204 let mut retry_result: Option<serde_json::Value> = None;
2205
2206 for attempt in 0..max_attempts {
2207 final_attempt = attempt;
2208 if attempt > 0 {
2209 tokio::time::sleep(retry_delay).await;
2210 }
2211 match async {
2212 let args_payload = trace_json_payload(
2213 &final_args,
2214 self.ctx.run_config().record_payloads,
2215 self.ctx.run_config().trace_payload_max_bytes,
2216 );
2217 tracing::debug!(tool.name = %name, tool.args = %args_payload, attempt = attempt, "tool_call");
2218 let exec_future = tool_clone.execute(tool_ctx.clone(), final_args.clone());
2219 let unwind_safe_future = std::panic::AssertUnwindSafe(
2220 tokio::time::timeout(self.tool_timeout, exec_future),
2221 );
2222 match futures::FutureExt::catch_unwind(unwind_safe_future).await {
2223 Ok(result) => result,
2224 Err(_panic) => Ok(Err(adk_core::AdkError::tool(format!(
2225 "tool '{}' panicked during execution",
2226 name
2227 )))),
2228 }
2229 }
2230 .instrument(tool_span.clone())
2231 .await
2232 {
2233 Ok(Ok(value)) => {
2234 let result_payload = trace_json_payload(
2235 &value,
2236 self.ctx.run_config().record_payloads,
2237 self.ctx.run_config().trace_payload_max_bytes,
2238 );
2239 tracing::debug!(tool.name = %name, tool.result = %result_payload, "tool_result");
2240 retry_result = Some(value);
2241 break;
2242 }
2243 Ok(Err(e)) => {
2244 last_error = e.to_string();
2245 if attempt + 1 < max_attempts {
2246 tracing::warn!(tool.name = %name, attempt = attempt, error = %last_error, "tool execution failed, retrying");
2247 } else {
2248 tracing::warn!(tool.name = %name, error = %last_error, "tool_error");
2249 }
2250 }
2251 Err(_) => {
2252 last_error = format!(
2253 "Tool '{}' timed out after {} seconds",
2254 name,
2255 self.tool_timeout.as_secs()
2256 );
2257 if attempt + 1 < max_attempts {
2258 tracing::warn!(tool.name = %name, attempt = attempt, timeout_secs = self.tool_timeout.as_secs(), "tool timed out, retrying");
2259 } else {
2260 tracing::warn!(tool.name = %name, timeout_secs = self.tool_timeout.as_secs(), "tool_timeout");
2261 }
2262 }
2263 }
2264 }
2265
2266 let tool_duration = tool_start.elapsed();
2267 let (tool_success, tool_error_message, function_response) = match retry_result {
2268 Some(value) => (true, None, value),
2269 None => (
2270 false,
2271 Some(last_error.clone()),
2272 serde_json::json!({ "error": last_error }),
2273 ),
2274 };
2275
2276 let outcome = ToolOutcome {
2277 tool_name: name.clone(),
2278 tool_args: final_args.clone(),
2279 success: tool_success,
2280 duration: tool_duration,
2281 error_message: tool_error_message.clone(),
2282 attempt: final_attempt,
2283 };
2284 tool_outcome_for_callback = Some(outcome);
2285
2286 {
2288 let mut guard = self.cb_mutex.lock().unwrap_or_else(|e| e.into_inner());
2289 if let Some(ref mut cb_state) = *guard {
2290 cb_state.record(tool_outcome_for_callback.as_ref().unwrap());
2291 }
2292 }
2293
2294 let final_function_response = if !tool_success {
2296 let mut fallback_result = None;
2297 let error_msg = tool_error_message.clone().unwrap_or_default();
2298 for callback in self.on_tool_error_callbacks.as_ref() {
2299 match callback(
2300 self.ctx.clone() as Arc<dyn CallbackContext>,
2301 tool.clone(),
2302 final_args.clone(),
2303 error_msg.clone(),
2304 )
2305 .await
2306 {
2307 Ok(Some(result)) => {
2308 fallback_result = Some(result);
2309 break;
2310 }
2311 Ok(None) => continue,
2312 Err(e) => {
2313 tracing::warn!(error = %e, "on_tool_error callback failed");
2314 break;
2315 }
2316 }
2317 }
2318 fallback_result.unwrap_or(function_response)
2319 } else {
2320 function_response
2321 };
2322
2323 let confirmation_decision = tool_actions.tool_confirmation_decision;
2324 tool_actions = tool_ctx.actions();
2325 if tool_actions.tool_confirmation_decision.is_none() {
2326 tool_actions.tool_confirmation_decision = confirmation_decision;
2327 }
2328 executed_tool = Some(tool.clone());
2329 executed_tool_response = Some(final_function_response.clone());
2330 response_content = Some(Content {
2331 role: "function".to_string(),
2332 parts: vec![Part::FunctionResponse {
2333 function_response: FunctionResponseData::from_tool_result(
2334 name.clone(),
2335 final_function_response,
2336 ),
2337 id: id.clone(),
2338 annotations: None,
2339 }],
2340 });
2341 } else {
2342 response_content = Some(Content {
2343 role: "function".to_string(),
2344 parts: vec![Part::FunctionResponse {
2345 function_response: FunctionResponseData::new(
2346 name.clone(),
2347 serde_json::json!({
2348 "error": format!("Tool {} not found", name)
2349 }),
2350 ),
2351 id: id.clone(),
2352 annotations: None,
2353 }],
2354 });
2355 }
2356 }
2357
2358 let mut response_content = response_content.expect("tool response content is set");
2360 if run_after_tool_callbacks {
2361 let outcome_ctx: Arc<dyn CallbackContext> = match tool_outcome_for_callback {
2362 Some(outcome) => Arc::new(ToolOutcomeCallbackContext {
2363 inner: self.ctx.clone() as Arc<dyn CallbackContext>,
2364 outcome,
2365 }),
2366 None => self.ctx.clone() as Arc<dyn CallbackContext>,
2367 };
2368 let cb_ctx: Arc<dyn CallbackContext> =
2369 Arc::new(ToolCallbackContext::new(outcome_ctx, name.clone(), final_args.clone()));
2370 for callback in self.after_tool_callbacks.as_ref() {
2371 match callback(cb_ctx.clone()).await {
2372 Ok(Some(modified)) => {
2373 response_content = modified;
2374 break;
2375 }
2376 Ok(None) => continue,
2377 Err(e) => {
2378 response_content = Content {
2379 role: "function".to_string(),
2380 parts: vec![Part::FunctionResponse {
2381 function_response: FunctionResponseData::new(
2382 name.clone(),
2383 serde_json::json!({ "error": e.to_string() }),
2384 ),
2385 id: id.clone(),
2386 annotations: None,
2387 }],
2388 };
2389 break;
2390 }
2391 }
2392 }
2393 if let (Some(tool_ref), Some(tool_resp)) = (&executed_tool, executed_tool_response) {
2394 for callback in self.after_tool_callbacks_full.as_ref() {
2395 match callback(
2396 cb_ctx.clone(),
2397 tool_ref.clone(),
2398 final_args.clone(),
2399 tool_resp.clone(),
2400 )
2401 .await
2402 {
2403 Ok(Some(modified_value)) => {
2404 response_content = Content {
2405 role: "function".to_string(),
2406 parts: vec![Part::FunctionResponse {
2407 function_response: FunctionResponseData::from_tool_result(
2408 name.clone(),
2409 modified_value,
2410 ),
2411 id: id.clone(),
2412 annotations: None,
2413 }],
2414 };
2415 break;
2416 }
2417 Ok(None) => continue,
2418 Err(e) => {
2419 response_content = Content {
2420 role: "function".to_string(),
2421 parts: vec![Part::FunctionResponse {
2422 function_response: FunctionResponseData::new(
2423 name.clone(),
2424 serde_json::json!({ "error": e.to_string() }),
2425 ),
2426 id: id.clone(),
2427 annotations: None,
2428 }],
2429 };
2430 break;
2431 }
2432 }
2433 }
2434 }
2435
2436 #[cfg(feature = "enhanced-plugins")]
2439 if let Some(epm) = self.enhanced_plugin_manager.as_ref()
2440 && let Some(tool_ref) = &executed_tool
2441 {
2442 let result_value = response_content
2444 .parts
2445 .iter()
2446 .find_map(|p| {
2447 if let Part::FunctionResponse { function_response, .. } = p {
2448 Some(function_response.response.clone())
2449 } else {
2450 None
2451 }
2452 })
2453 .unwrap_or(serde_json::json!(null));
2454
2455 match epm
2456 .run_after_tool_call(
2457 tool_ref.clone(),
2458 &final_args,
2459 result_value,
2460 self.ctx.clone() as Arc<dyn CallbackContext>,
2461 )
2462 .await
2463 {
2464 Ok(adk_plugin::AfterToolCallResult::Continue(modified_result)) => {
2465 response_content = Content {
2466 role: "function".to_string(),
2467 parts: vec![Part::FunctionResponse {
2468 function_response: FunctionResponseData::from_tool_result(
2469 name.clone(),
2470 modified_result,
2471 ),
2472 id: id.clone(),
2473 annotations: None,
2474 }],
2475 };
2476 }
2477 Err(e) => {
2478 response_content = Content {
2479 role: "function".to_string(),
2480 parts: vec![Part::FunctionResponse {
2481 function_response: FunctionResponseData::new(
2482 name.clone(),
2483 serde_json::json!({ "error": e.to_string() }),
2484 ),
2485 id: id.clone(),
2486 annotations: None,
2487 }],
2488 };
2489 }
2490 }
2491 }
2492 }
2493
2494 let escalate_or_skip = tool_actions.escalate || tool_actions.skip_summarization;
2495 Ok(ToolExecutionResult {
2496 index,
2497 content: response_content,
2498 actions: tool_actions,
2499 escalate_or_skip,
2500 })
2501 }
2502}
2503
2504#[async_trait]
2505impl Agent for LlmAgent {
2506 fn name(&self) -> &str {
2507 &self.name
2508 }
2509
2510 fn description(&self) -> &str {
2511 &self.description
2512 }
2513
2514 fn sub_agents(&self) -> &[Arc<dyn Agent>] {
2515 &self.sub_agents
2516 }
2517
2518 fn capabilities(&self) -> adk_core::AgentCapabilities {
2519 adk_core::AgentCapabilities {
2520 runtime_tools: true,
2521 handoff: true,
2522 relationship_confirmation: true,
2523 checkpoint_resume: false,
2524 shared_state: true,
2525 invocation_metadata: true,
2526 }
2527 }
2528
2529 #[adk_telemetry::instrument(
2530 skip(self, ctx),
2531 fields(
2532 agent.name = %self.name,
2533 agent.description = %self.description,
2534 invocation.id = %ctx.invocation_id(),
2535 user.id = %ctx.user_id(),
2536 session.id = %ctx.session_id()
2537 )
2538 )]
2539 async fn run(&self, ctx: Arc<dyn InvocationContext>) -> Result<adk_core::EventStream> {
2540 adk_telemetry::info!("Starting agent execution");
2541 let ctx = Self::apply_input_guardrails(ctx, self.input_guardrails.clone()).await?;
2542
2543 let agent_name = self.name.clone();
2544 let invocation_id = ctx.invocation_id().to_string();
2545 let model = self.model.clone();
2546 let prompt_config = PromptConfig::from_agent(self);
2547 let tool_setup = ToolSetup::from_agent(self);
2548 let output_key = self.output_key.clone();
2549 let output_max_retries = self.output_max_retries;
2550 let generate_content_config = self.generate_content_config.clone();
2551 let max_iterations = self.max_iterations;
2552 let tool_timeout = self.tool_timeout;
2553 let before_agent_callbacks = self.before_callbacks.clone();
2555 let after_agent_callbacks = self.after_callbacks.clone();
2556 let before_model_callbacks = self.before_model_callbacks.clone();
2557 let after_model_callbacks = self.after_model_callbacks.clone();
2558 let before_tool_callbacks = self.before_tool_callbacks.clone();
2559 let after_tool_callbacks = self.after_tool_callbacks.clone();
2560 let on_tool_error_callbacks = self.on_tool_error_callbacks.clone();
2561 let after_tool_callbacks_full = self.after_tool_callbacks_full.clone();
2562 let default_retry_budget = self.default_retry_budget.clone();
2563 let tool_retry_budgets = self.tool_retry_budgets.clone();
2564 let circuit_breaker_threshold = self.circuit_breaker_threshold;
2565 let tool_confirmation_policy = self.tool_confirmation_policy.clone();
2566 let tool_guardrails = Arc::clone(&self.tool_guardrails);
2567 let output_guardrails = self.output_guardrails.clone();
2568 let agent_tool_execution_strategy = self.tool_execution_strategy;
2569 let parallelize_agent_delegations = self.parallelize_agent_delegations;
2570 #[cfg(feature = "enhanced-plugins")]
2571 let enhanced_plugin_manager = self.enhanced_plugin_manager.clone();
2572
2573 let s = stream! {
2574 let confirmation_decisions =
2575 ctx.run_config().tool_confirmation_decisions.clone();
2576 let confirmation_fingerprints =
2577 ctx.run_config().tool_confirmation_fingerprints.clone();
2578 let confirmation_handler = ctx.run_config().tool_confirmation_handler.clone();
2579
2580 for callback in before_agent_callbacks.as_ref() {
2584 match callback(ctx.clone() as Arc<dyn CallbackContext>).await {
2585 Ok(Some(content)) => {
2586 let mut early_event = Event::new(&invocation_id);
2588 early_event.author = agent_name.clone();
2589 early_event.llm_response.content = Some(content);
2590 yield Ok(early_event);
2591
2592 for after_callback in after_agent_callbacks.as_ref() {
2594 match after_callback(ctx.clone() as Arc<dyn CallbackContext>).await {
2595 Ok(Some(after_content)) => {
2596 let mut after_event = Event::new(&invocation_id);
2597 after_event.author = agent_name.clone();
2598 after_event.llm_response.content = Some(after_content);
2599 yield Ok(after_event);
2600 return;
2601 }
2602 Ok(None) => continue,
2603 Err(e) => {
2604 yield Err(e);
2605 return;
2606 }
2607 }
2608 }
2609 return;
2610 }
2611 Ok(None) => {
2612 continue;
2614 }
2615 Err(e) => {
2616 yield Err(e);
2618 return;
2619 }
2620 }
2621 }
2622
2623 let mut conversation_history = match prompt_config
2625 .prepare_conversation(&ctx, &agent_name)
2626 .await
2627 {
2628 Ok(history) => history,
2629 Err(error) => {
2630 yield Err(error);
2631 return;
2632 }
2633 };
2634
2635 let resolved_tools = match tool_setup.resolve(&ctx).await {
2636 Ok(tools) => tools,
2637 Err(error) => {
2638 yield Err(error);
2639 return;
2640 }
2641 };
2642 let mut tool_map = resolved_tools.map;
2643 let mut tool_declarations = resolved_tools.declarations;
2644 let mut valid_transfer_targets = resolved_tools.transfer_targets;
2645
2646 let mut circuit_breaker_state = circuit_breaker_threshold.map(CircuitBreakerState::new);
2649
2650 let mut last_interaction_id: Option<String> = None;
2659
2660 let mut iteration = 0;
2662 let mut schema_retry_count: usize = 0;
2663
2664 loop {
2665 if ctx.is_cancelled() {
2668 tracing::info!(agent.name = %agent_name, "invocation cancelled — stopping agent loop");
2669 return;
2670 }
2671 iteration += 1;
2672 if iteration > max_iterations {
2673 yield Err(adk_core::AdkError::agent(
2674 format!("Max iterations ({max_iterations}) exceeded")
2675 ));
2676 return;
2677 }
2678
2679 let config = build_generation_config(
2680 generate_content_config.as_ref(),
2681 prompt_config.output_schema.as_ref(),
2682 ctx.run_config().cached_content.as_deref(),
2683 );
2684
2685 let mut request = LlmRequest {
2686 model: model.name().to_string(),
2687 contents: conversation_history.clone(),
2688 tools: tool_declarations.clone(),
2689 config,
2690 previous_response_id: last_interaction_id.clone(),
2697 };
2698
2699 if let Err(error) = tool_setup.process_llm_request(&ctx, &mut request).await {
2700 yield Err(error);
2701 return;
2702 }
2703
2704 #[cfg(feature = "enhanced-plugins")]
2708 let (request, model_response_override_from_plugin) = {
2709 if let Some(epm) = &enhanced_plugin_manager {
2710 match epm.run_before_model_call(request, ctx.clone() as Arc<dyn CallbackContext>).await {
2711 Ok(BeforeModelCallResult::Continue(modified_request)) => {
2712 (modified_request, None)
2713 }
2714 Ok(BeforeModelCallResult::ShortCircuit(response)) => {
2715 (LlmRequest::new("", vec![]), Some(response))
2717 }
2718 Err(e) => {
2719 yield Err(e);
2720 return;
2721 }
2722 }
2723 } else {
2724 (request, None)
2725 }
2726 };
2727 #[cfg(not(feature = "enhanced-plugins"))]
2728 let model_response_override_from_plugin: Option<LlmResponse> = None;
2729
2730 let mut current_request = request;
2733 let mut model_response_override = model_response_override_from_plugin;
2734 if model_response_override.is_none() {
2735 for callback in before_model_callbacks.as_ref() {
2736 match callback(ctx.clone() as Arc<dyn CallbackContext>, current_request.clone()).await {
2737 Ok(BeforeModelResult::Continue(modified_request)) => {
2738 current_request = modified_request;
2740 }
2741 Ok(BeforeModelResult::Skip(response)) => {
2742 model_response_override = Some(response);
2744 break;
2745 }
2746 Err(e) => {
2747 yield Err(e);
2749 return;
2750 }
2751 }
2752 }
2753 }
2754 let request = current_request;
2755
2756 let mut accumulated_content: Option<Content> = None;
2758 let mut final_provider_metadata: Option<serde_json::Value> = None;
2759
2760 if let Some(cached_response) = model_response_override {
2761 accumulated_content = cached_response.content.clone();
2764 final_provider_metadata = cached_response.provider_metadata.clone();
2765 normalize_option_content(&mut accumulated_content);
2766 if let Some(content) = accumulated_content.take() {
2767 let has_function_calls = content
2768 .parts
2769 .iter()
2770 .any(|part| matches!(part, Part::FunctionCall { .. }));
2771 let content = if has_function_calls || Self::continues_turn(final_provider_metadata.as_ref()) {
2772 content
2773 } else {
2774 Self::apply_output_guardrails(output_guardrails.as_ref(), content).await?
2775 };
2776 accumulated_content = Some(content);
2777 }
2778
2779 let mut cached_event = Event::new(&invocation_id);
2780 cached_event.author = agent_name.clone();
2781 cached_event.llm_response.content = accumulated_content.clone();
2782 cached_event.llm_response.turn_complete = cached_response.turn_complete;
2783 cached_event.llm_response.citation_metadata = cached_response.citation_metadata.clone();
2784 cached_event.llm_response.provider_metadata = cached_response.provider_metadata.clone();
2785 cached_event.llm_response.interaction_id = cached_response.interaction_id.clone();
2787 if cached_response.interaction_id.is_some() {
2788 last_interaction_id = cached_response.interaction_id.clone();
2789 }
2790 cached_event.llm_request = Some(serde_json::to_string(&request).unwrap_or_default());
2791 cached_event.provider_metadata.insert("gcp.vertex.agent.llm_request".to_string(), serde_json::to_string(&request).unwrap_or_default());
2792 cached_event.provider_metadata.insert("gcp.vertex.agent.llm_response".to_string(), serde_json::to_string(&cached_response).unwrap_or_default());
2793
2794 if let Some(ref content) = accumulated_content {
2796 cached_event.long_running_tool_ids =
2797 collect_long_running_tool_ids(&tool_map, content);
2798 }
2799
2800 yield Ok(cached_event);
2801 } else {
2802 let request_json = serde_json::to_string(&request).unwrap_or_default();
2804 let trace_request_json = trace_json_payload(
2805 &request,
2806 ctx.run_config().record_payloads,
2807 ctx.run_config().trace_payload_max_bytes,
2808 );
2809
2810 let llm_ts = std::time::SystemTime::now()
2812 .duration_since(std::time::UNIX_EPOCH)
2813 .unwrap_or_default()
2814 .as_nanos();
2815 let llm_event_id = format!("{}_llm_{}", invocation_id, llm_ts);
2816 let llm_span = tracing::info_span!(
2817 "call_llm",
2818 "gcp.vertex.agent.event_id" = %llm_event_id,
2819 "gcp.vertex.agent.invocation_id" = %invocation_id,
2820 "gcp.vertex.agent.session_id" = %ctx.session_id(),
2821 "gen_ai.conversation.id" = %ctx.session_id(),
2822 "gcp.vertex.agent.llm_request" = %trace_request_json,
2823 "gcp.vertex.agent.llm_response" = tracing::field::Empty );
2825 use adk_core::StreamingMode;
2838 let streaming_mode = ctx.run_config().streaming_mode;
2839 let should_stream_to_client = matches!(streaming_mode, StreamingMode::SSE | StreamingMode::Bidi)
2840 && output_guardrails.is_empty();
2841
2842 let mut response_stream = model
2844 .generate_content(request, true)
2845 .instrument(llm_span.clone())
2846 .await?;
2847
2848 use futures::StreamExt;
2849
2850 let mut last_chunk: Option<LlmResponse> = None;
2852
2853 while let Some(chunk_result) =
2855 response_stream.next().instrument(llm_span.clone()).await
2856 {
2857 if ctx.is_cancelled() {
2861 llm_span.in_scope(|| {
2862 tracing::info!(agent.name = %agent_name, "invocation cancelled during LLM streaming")
2863 });
2864 return;
2865 }
2866 let mut chunk = match chunk_result {
2867 Ok(c) => c,
2868 Err(e) => {
2869 yield Err(e);
2870 return;
2871 }
2872 };
2873
2874 for callback in after_model_callbacks.as_ref() {
2877 match callback(ctx.clone() as Arc<dyn CallbackContext>, chunk.clone())
2878 .instrument(llm_span.clone())
2879 .await
2880 {
2881 Ok(Some(modified_chunk)) => {
2882 chunk = modified_chunk;
2884 break;
2885 }
2886 Ok(None) => {
2887 continue;
2889 }
2890 Err(e) => {
2891 yield Err(e);
2893 return;
2894 }
2895 }
2896 }
2897
2898 normalize_option_content(&mut chunk.content);
2899
2900 if let Some(chunk_content) = chunk.content.clone() {
2902 if !chunk.partial && chunk.provider_metadata.as_ref()
2903 .and_then(|metadata| metadata.get("content_complete"))
2904 .and_then(serde_json::Value::as_bool) == Some(true)
2905 {
2906 accumulated_content = Some(chunk_content);
2907 } else if let Some(ref mut acc) = accumulated_content {
2908 acc.parts.extend(chunk_content.parts);
2909 } else {
2910 accumulated_content = Some(chunk_content);
2911 }
2912 }
2913
2914 if should_stream_to_client {
2916 let long_running_tool_ids = chunk
2917 .content
2918 .as_ref()
2919 .map(|content| collect_long_running_tool_ids(&tool_map, content))
2920 .unwrap_or_default();
2921 let mut event = build_partial_llm_event(
2922 &llm_event_id,
2923 &invocation_id,
2924 &agent_name,
2925 &request_json,
2926 &chunk,
2927 long_running_tool_ids,
2928 );
2929 if !chunk.partial {
2933 event.llm_response.content = accumulated_content.clone();
2934 if let Some(metadata) = event.llm_response.provider_metadata
2935 .get_or_insert_with(|| serde_json::json!({}))
2936 .as_object_mut()
2937 {
2938 metadata.insert("content_complete".into(), true.into());
2939 }
2940 }
2941 yield Ok(event);
2942 }
2943
2944 if chunk.interaction_id.is_some() {
2948 last_interaction_id = chunk.interaction_id.clone();
2949 }
2950
2951 last_chunk = Some(chunk.clone());
2953
2954 if chunk.turn_complete {
2956 break;
2957 }
2958 }
2959
2960 if let Some(last) = &last_chunk {
2961 final_provider_metadata = last.provider_metadata.clone();
2962 }
2963
2964 if !should_stream_to_client {
2966 if let Some(content) = accumulated_content.take() {
2967 let has_function_calls = content
2968 .parts
2969 .iter()
2970 .any(|part| matches!(part, Part::FunctionCall { .. }));
2971 let content = if has_function_calls || Self::continues_turn(final_provider_metadata.as_ref()) {
2972 content
2973 } else {
2974 Self::apply_output_guardrails(output_guardrails.as_ref(), content)
2975 .instrument(llm_span.clone())
2976 .await?
2977 };
2978 accumulated_content = Some(content);
2979 }
2980
2981 let long_running_tool_ids = accumulated_content
2982 .as_ref()
2983 .map(|content| collect_long_running_tool_ids(&tool_map, content))
2984 .unwrap_or_default();
2985 yield Ok(build_final_llm_event(
2986 &llm_event_id,
2987 &invocation_id,
2988 &agent_name,
2989 &request_json,
2990 accumulated_content.as_ref(),
2991 last_chunk.as_ref(),
2992 long_running_tool_ids,
2993 ));
2994 }
2995
2996 if let Some(ref last) = last_chunk
3007 && let Some(ref code) = last.error_code
3008 {
3009 let message = last
3010 .error_message
3011 .clone()
3012 .unwrap_or_else(|| "provider reported a terminal error".to_string());
3013 llm_span.in_scope(|| {
3014 tracing::error!(
3015 error.code = %code,
3016 error.message = %message,
3017 agent = %agent_name,
3018 "model reported a terminal error"
3019 )
3020 });
3021 let mut details = adk_core::ErrorDetails::default();
3028 details
3029 .metadata
3030 .insert("provider_error_code".to_string(), serde_json::json!(code));
3031 let provider_error = adk_core::AdkError::new(
3032 adk_core::ErrorComponent::Model,
3033 adk_core::ErrorCategory::Internal,
3034 "model.provider_error",
3035 format!("{code}: {message}"),
3036 )
3037 .with_details(details);
3038 yield Err(provider_error);
3039 return;
3040 }
3041
3042 if let Some(ref content) = accumulated_content {
3044 let response_json = trace_json_payload(
3045 content,
3046 ctx.run_config().record_payloads,
3047 ctx.run_config().trace_payload_max_bytes,
3048 );
3049 llm_span.record("gcp.vertex.agent.llm_response", &response_json);
3050 }
3051 }
3052
3053 #[cfg(feature = "enhanced-plugins")]
3057 if let Some(epm) = &enhanced_plugin_manager
3058 && let Some(ref content) = accumulated_content {
3059 let response_for_hook = LlmResponse {
3060 content: Some(content.clone()),
3061 provider_metadata: final_provider_metadata.clone(),
3062 ..Default::default()
3063 };
3064 match epm.run_after_model_call(response_for_hook, ctx.clone() as Arc<dyn CallbackContext>).await {
3065 Ok(adk_plugin::AfterModelCallResult::Continue(modified_response)) => {
3066 accumulated_content = modified_response.content;
3067 if modified_response.provider_metadata.is_some() {
3068 final_provider_metadata = modified_response.provider_metadata;
3069 }
3070 }
3071 Err(e) => {
3072 yield Err(e);
3073 return;
3074 }
3075 }
3076 }
3077
3078 let function_call_names: Vec<String> = accumulated_content.as_ref()
3080 .map(|c| c.parts.iter()
3081 .filter_map(|p| {
3082 if let Part::FunctionCall { name, .. } = p {
3083 Some(name.clone())
3084 } else {
3085 None
3086 }
3087 })
3088 .collect())
3089 .unwrap_or_default();
3090
3091 let has_function_calls = !function_call_names.is_empty();
3092 let continues_turn = Self::continues_turn(final_provider_metadata.as_ref());
3093
3094 let all_calls_are_long_running = has_function_calls && function_call_names.iter().all(|name| {
3098 tool_map.get(name)
3099 .map(|t| t.is_long_running())
3100 .unwrap_or(false)
3101 });
3102
3103 if let Some(ref content) = accumulated_content {
3105 conversation_history.push(Self::augment_content_for_history(
3106 content,
3107 final_provider_metadata.as_ref(),
3108 ));
3109 }
3110
3111 if !has_function_calls && continues_turn {
3114 continue;
3115 }
3116
3117 if !has_function_calls {
3118 if let Some(schema) = &prompt_config.output_schema {
3123 let text = accumulated_content
3124 .as_ref()
3125 .map(|c| {
3126 c.parts
3127 .iter()
3128 .filter_map(|p| {
3129 if let Part::Text { text } = p {
3130 Some(text.as_str())
3131 } else {
3132 None
3133 }
3134 })
3135 .collect::<Vec<_>>()
3136 .join("")
3137 })
3138 .unwrap_or_default();
3139
3140 if !text.is_empty()
3141 && let Err(validation_error) = validate_output_against_schema(&text, schema)
3142 {
3143 if schema_retry_count >= output_max_retries {
3144 yield Err(adk_core::AdkError::agent(format!(
3145 "output schema validation failed after {} attempts",
3146 output_max_retries
3147 )));
3148 return;
3149 }
3150 schema_retry_count += 1;
3151
3152 let correction = format!(
3154 "Your output did not match the required schema. Error: {}. Please produce valid JSON matching the schema.",
3155 validation_error
3156 );
3157 conversation_history.push(Content {
3158 role: "user".to_string(),
3159 parts: vec![Part::Text { text: correction }],
3160 });
3161 continue;
3162 }
3163 }
3164
3165 if let Some(ref output_key) = output_key
3168 && let Some(ref content) = accumulated_content
3169 {
3170 let mut text_parts = String::new();
3171 for part in &content.parts {
3172 if let Part::Text { text } = part {
3173 text_parts.push_str(text);
3174 }
3175 }
3176 if !text_parts.is_empty() {
3177 let mut state_event = Event::new(&invocation_id);
3178 state_event.author = agent_name.clone();
3179 state_event.actions.state_delta.insert(
3180 output_key.clone(),
3181 serde_json::Value::String(text_parts),
3182 );
3183 yield Ok(state_event);
3184 }
3185 }
3186
3187 if let Some(ref content) = accumulated_content {
3190 let response_json = trace_json_payload(
3191 content,
3192 ctx.run_config().record_payloads,
3193 ctx.run_config().trace_payload_max_bytes,
3194 );
3195 tracing::Span::current().record("gcp.vertex.agent.llm_response", &response_json);
3196 }
3197
3198 tracing::info!(agent.name = %agent_name, "Agent execution complete");
3199 break;
3200 }
3201
3202 if let Some(content) = &accumulated_content {
3204 let strategy = ToolDispatchMode::resolve(
3207 agent_tool_execution_strategy.unwrap_or(ToolExecutionStrategy::Sequential),
3208 parallelize_agent_delegations,
3209 );
3210
3211 let fc_parts = collect_function_calls(content, &invocation_id);
3212
3213 let mut transfer_handled = false;
3217 for call in &fc_parts {
3218 if call.name == "transfer_to_agent" {
3219 let target_agent = call
3220 .args
3221 .get("agent_name")
3222 .and_then(|value| value.as_str())
3223 .unwrap_or_default()
3224 .to_string();
3225
3226 let valid_target = valid_transfer_targets.iter().any(|n| n == &target_agent);
3227 if !valid_target {
3228 let error_content = Content {
3229 role: "function".to_string(),
3230 parts: vec![Part::FunctionResponse {
3231 function_response: FunctionResponseData::new(
3232 call.name.clone(),
3233 serde_json::json!({
3234 "error": format!(
3235 "Agent '{}' not found. Available agents: {:?}",
3236 target_agent, valid_transfer_targets
3237 )
3238 }),
3239 ),
3240 id: call.id.clone(),
3241 annotations: None,
3242 }],
3243 };
3244 conversation_history.push(error_content.clone());
3245 let mut error_event = Event::new(&invocation_id);
3246 error_event.author = agent_name.clone();
3247 error_event.llm_response.content = Some(error_content);
3248 yield Ok(error_event);
3249 continue;
3250 }
3251
3252 let mut transfer_event = Event::new(&invocation_id);
3253 transfer_event.author = agent_name.clone();
3254 transfer_event.actions.transfer_to_agent = Some(target_agent);
3255 yield Ok(transfer_event);
3256 transfer_handled = true;
3257 break;
3258 }
3259 }
3260 if transfer_handled {
3261 return;
3262 }
3263
3264 let mut fc_parts: Vec<_> = fc_parts
3266 .into_iter()
3267 .filter(|call| {
3268 if call.name == "transfer_to_agent" {
3269 return false;
3270 }
3271 if let Some(tool) = tool_map.get(&call.name)
3272 && tool.is_builtin()
3273 {
3274 adk_telemetry::debug!(tool.name = %call.name, "skipping built-in tool execution");
3275 return false;
3276 }
3277 true
3278 })
3279 .collect();
3280
3281 for call in &mut fc_parts {
3285 match screen_tool_call(&tool_guardrails, &call.name, &call.args).await {
3286 ToolScreening::Allow(args) => call.args = args,
3287 ToolScreening::Deny(reason) => {
3288 call.guardrail_denial = Some(reason);
3289 }
3290 }
3291 }
3292
3293 let mut confirmation_interrupted = false;
3297 for call in &fc_parts {
3298 if confirmation_handler.is_none()
3299 && call.guardrail_denial.is_none()
3300 && (tool_confirmation_policy.requires_confirmation(&call.name)
3301 || ctx.requires_tool_confirmation(&call.name))
3302 && static_confirmation_decision(
3303 &confirmation_decisions,
3304 &confirmation_fingerprints,
3305 &call.function_call_id,
3306 &call.name,
3307 &call.args,
3308 )
3309 .is_none()
3310 {
3311 let request = ToolConfirmationRequest {
3312 tool_name: call.name.clone(),
3313 function_call_id: Some(call.function_call_id.clone()),
3314 args: call.args.clone(),
3315 };
3316
3317 let mut ce = Event::new(&invocation_id);
3318 ce.author = agent_name.clone();
3319 ce.llm_response.interrupted = true;
3320 ce.llm_response.turn_complete = true;
3321 ce.llm_response.content = Some(Content {
3322 role: "model".to_string(),
3323 parts: vec![Part::Text {
3324 text: format!(
3325 "Tool confirmation required for '{}'. Provide approve/deny decision to continue.",
3326 call.name
3327 ),
3328 }],
3329 });
3330 ce.actions.tool_confirmation = Some(request);
3331 yield Ok(ce);
3332 confirmation_interrupted = true;
3333 break;
3334 }
3335 }
3336 if confirmation_interrupted {
3337 return;
3338 }
3339
3340 let cb_mutex = std::sync::Mutex::new(circuit_breaker_state.take());
3342
3343 let concurrency_manager = adk_core::ToolConcurrencyManager::new(
3346 &ctx.run_config().tool_concurrency,
3347 );
3348
3349 let (progress_tx, mut progress_rx) =
3354 tokio::sync::mpsc::channel::<Event>(TOOL_PROGRESS_CAPACITY);
3355
3356 let executor = ToolExecutor {
3357 ctx: ctx.clone(),
3358 tool_map: &tool_map,
3359 tool_retry_budgets: &tool_retry_budgets,
3360 default_retry_budget: &default_retry_budget,
3361 before_tool_callbacks: &before_tool_callbacks,
3362 after_tool_callbacks: &after_tool_callbacks,
3363 after_tool_callbacks_full: &after_tool_callbacks_full,
3364 on_tool_error_callbacks: &on_tool_error_callbacks,
3365 tool_confirmation_policy: &tool_confirmation_policy,
3366 cb_mutex: &cb_mutex,
3367 invocation_id: &invocation_id,
3368 concurrency_manager: &concurrency_manager,
3369 progress_tx: progress_tx.clone(),
3370 tool_timeout,
3371 confirmation_decisions: &confirmation_decisions,
3372 confirmation_fingerprints: &confirmation_fingerprints,
3373 confirmation_lock: tokio::sync::Mutex::new(()),
3374 approval_failed: tokio::sync::watch::channel(false).0,
3375 #[cfg(feature = "enhanced-plugins")]
3376 enhanced_plugin_manager: &enhanced_plugin_manager,
3377 };
3378
3379 if ctx.is_cancelled() {
3382 tracing::info!(agent.name = %agent_name, "invocation cancelled before tool dispatch");
3383 return;
3384 }
3385
3386 let mut results = {
3391 let dispatch = async {
3392 let results: Vec<ToolExecutionResult> = match strategy {
3393 ToolDispatchMode::Sequential => {
3394 let mut results = Vec::with_capacity(fc_parts.len());
3395 for call in fc_parts {
3396 results.push(executor.execute(call).await?);
3397 }
3398 results
3399 }
3400 ToolDispatchMode::Parallel => {
3401 use futures::StreamExt as _;
3402 let buffer_size = fc_parts.len().max(1);
3409 futures::stream::iter(
3410 fc_parts.into_iter().map(|call| executor.execute(call)),
3411 )
3412 .buffer_unordered(buffer_size)
3413 .collect::<Vec<_>>()
3415 .await
3416 .into_iter()
3417 .collect::<Result<Vec<_>>>()?
3418 }
3419 ToolDispatchMode::ParallelDelegations => {
3420 use futures::StreamExt as _;
3421
3422 let mut all_results = Vec::with_capacity(fc_parts.len());
3423 let mut calls = fc_parts.into_iter().peekable();
3424 while let Some(call) = calls.next() {
3425 let is_delegation = tool_map
3426 .get(&call.name)
3427 .is_some_and(|tool| tool.is_agent_delegation());
3428 if !is_delegation {
3429 all_results.push(executor.execute(call).await?);
3430 continue;
3431 }
3432
3433 let mut delegation_batch = vec![call];
3434 while calls.peek().is_some_and(|next| {
3435 tool_map.get(&next.name).is_some_and(|tool| {
3436 tool.is_agent_delegation()
3437 })
3438 }) {
3439 if let Some(next) = calls.next() {
3440 delegation_batch.push(next);
3441 }
3442 }
3443
3444 let buffer_size = delegation_batch.len();
3445 all_results.extend(
3446 futures::stream::iter(
3447 delegation_batch
3448 .into_iter()
3449 .map(|call| executor.execute(call)),
3450 )
3451 .buffer_unordered(buffer_size)
3452 .collect::<Vec<_>>()
3453 .await
3454 .into_iter()
3455 .collect::<Result<Vec<_>>>()?,
3456 );
3457 }
3458 all_results
3459 }
3460 ToolDispatchMode::Auto => {
3461 let (concurrent_fcs, sequential_fcs): (Vec<_>, Vec<_>) =
3464 fc_parts.into_iter().partition(|call| {
3465 tool_map.get(&call.name).is_some_and(|tool| {
3466 tool.is_read_only() && tool.is_concurrency_safe()
3467 })
3468 });
3469 let mut all_results = Vec::new();
3470
3471 if !concurrent_fcs.is_empty() {
3474 use futures::StreamExt as _;
3475 let buffer_size = concurrent_fcs.len().max(1);
3476 all_results.extend(
3477 futures::stream::iter(
3478 concurrent_fcs
3479 .into_iter()
3480 .map(|call| executor.execute(call)),
3481 )
3482 .buffer_unordered(buffer_size)
3483 .collect::<Vec<_>>()
3484 .await
3485 .into_iter()
3486 .collect::<Result<Vec<_>>>()?,
3487 );
3488 }
3489
3490 for call in sequential_fcs {
3492 all_results.push(executor.execute(call).await?);
3493 }
3494 all_results
3495 }
3496 };
3497 Ok::<_, adk_core::AdkError>(results)
3498 };
3499
3500 tokio::pin!(dispatch);
3506 let results = loop {
3507 tokio::select! {
3508 biased;
3509 Some(progress_event) = progress_rx.recv() => {
3510 yield Ok(progress_event);
3511 }
3512 done = &mut dispatch => break done,
3513 }
3514 };
3515 while let Ok(progress_event) = progress_rx.try_recv() {
3517 yield Ok(progress_event);
3518 }
3519 match results {
3520 Ok(results) => results,
3521 Err(error) => {
3522 yield Err(error);
3523 return;
3524 }
3525 }
3526 };
3527 results.sort_by_key(|r| r.index);
3529
3530 circuit_breaker_state = cb_mutex.into_inner().unwrap_or_else(|e| e.into_inner());
3532
3533 for result in results {
3536 if result.escalate_or_skip {
3537 return;
3538 }
3539
3540 conversation_history.push(result.content);
3541 }
3542
3543 let refreshed_tools = match tool_setup.resolve(&ctx).await {
3548 Ok(tools) => tools,
3549 Err(error) => {
3550 yield Err(error);
3551 return;
3552 }
3553 };
3554 tool_map = refreshed_tools.map;
3555 tool_declarations = refreshed_tools.declarations;
3556 valid_transfer_targets = refreshed_tools.transfer_targets;
3557 }
3558
3559 if all_calls_are_long_running {
3563 }
3567 }
3568
3569 for callback in after_agent_callbacks.as_ref() {
3572 match callback(ctx.clone() as Arc<dyn CallbackContext>).await {
3573 Ok(Some(content)) => {
3574 let mut after_event = Event::new(&invocation_id);
3576 after_event.author = agent_name.clone();
3577 after_event.llm_response.content = Some(content);
3578 yield Ok(after_event);
3579 break; }
3581 Ok(None) => {
3582 continue;
3584 }
3585 Err(e) => {
3586 yield Err(e);
3588 return;
3589 }
3590 }
3591 }
3592 };
3593
3594 Ok(Box::pin(s))
3595 }
3596}
3597
3598#[cfg(test)]
3599mod run_helper_tests {
3600 use super::*;
3601
3602 #[test]
3603 fn generation_config_layers_schema_and_cached_content() {
3604 let base =
3605 adk_core::GenerateContentConfig { temperature: Some(0.25), ..Default::default() };
3606 let schema = serde_json::json!({"type": "object"});
3607
3608 let config = build_generation_config(Some(&base), Some(&schema), Some("cached/example"))
3609 .expect("config should be present");
3610
3611 assert_eq!(config.temperature, Some(0.25));
3612 assert_eq!(config.response_schema, Some(schema));
3613 assert_eq!(config.cached_content.as_deref(), Some("cached/example"));
3614 }
3615
3616 #[test]
3617 fn function_calls_preserve_order_and_create_fallback_ids() {
3618 let content = Content {
3619 role: "model".to_string(),
3620 parts: vec![
3621 Part::Text { text: "before".to_string() },
3622 Part::FunctionCall {
3623 name: "first".to_string(),
3624 args: serde_json::json!({"value": 1}),
3625 id: None,
3626 thought_signature: None,
3627 },
3628 Part::FunctionCall {
3629 name: "second".to_string(),
3630 args: serde_json::json!({"value": 2}),
3631 id: Some("provider-id".to_string()),
3632 thought_signature: None,
3633 },
3634 ],
3635 };
3636
3637 let calls = collect_function_calls(&content, "invocation");
3638
3639 assert_eq!(calls.len(), 2);
3640 assert_eq!(calls[0].index, 0);
3641 assert_eq!(calls[0].name, "first");
3642 assert_eq!(calls[0].function_call_id, "invocation_first_0");
3643 assert_eq!(calls[1].index, 1);
3644 assert_eq!(calls[1].name, "second");
3645 assert_eq!(calls[1].function_call_id, "provider-id");
3646 }
3647
3648 #[test]
3649 fn partial_stream_events_do_not_repeat_large_payloads() {
3650 let marker = "PRIVATE-REQUEST-PAYLOAD";
3651 let request_json = format!("{marker}{}", "x".repeat(128 * 1024));
3652 let chunk = LlmResponse {
3653 content: Some(Content::new("model").with_text("x")),
3654 partial: true,
3655 ..Default::default()
3656 };
3657 let mut serialized_bytes = 0;
3658
3659 for index in 0..64 {
3660 let event = build_partial_llm_event(
3661 &format!("event-{index}"),
3662 "invocation",
3663 "agent",
3664 &request_json,
3665 &chunk,
3666 Vec::new(),
3667 );
3668 let serialized = serde_json::to_string(&event).expect("event should serialize");
3669
3670 assert!(!serialized.contains(marker));
3671 assert!(event.llm_request.is_none());
3672 assert!(!event.provider_metadata.contains_key("gcp.vertex.agent.llm_request"));
3673 assert!(!event.provider_metadata.contains_key("gcp.vertex.agent.llm_response"));
3674 serialized_bytes += serialized.len();
3675 }
3676
3677 assert!(
3678 serialized_bytes < 1024 * 1024,
3679 "64 tiny chunks unexpectedly serialized to {serialized_bytes} bytes"
3680 );
3681 }
3682}