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::{GuardrailSet, enforce_guardrails},
27 skill_shim::{SelectionPolicy, SkillIndex, apply_skill_injection},
28 tool_call_markup::normalize_option_content,
29 workflow::with_user_content_override,
30};
31
32pub const DEFAULT_MAX_ITERATIONS: u32 = 100;
34
35pub const DEFAULT_TOOL_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(300);
37
38fn trace_json_payload<T: serde::Serialize>(
39 value: &T,
40 record_payloads: bool,
41 max_bytes: usize,
42) -> String {
43 let json = serde_json::to_string(value).unwrap_or_default();
44 if cfg!(feature = "record-payloads") && record_payloads {
45 return json;
46 }
47
48 let max_bytes = max_bytes.max(32);
49 if json.len() <= max_bytes {
50 return json;
51 }
52
53 let mut end = max_bytes;
54 while !json.is_char_boundary(end) {
55 end -= 1;
56 }
57 format!("{}...[truncated {} bytes]", &json[..end], json.len() - end)
58}
59
60#[derive(Debug, Clone)]
61struct PendingToolCall {
62 index: usize,
63 name: String,
64 args: serde_json::Value,
65 id: Option<String>,
66 function_call_id: String,
67}
68
69fn build_generation_config(
70 base: Option<&adk_core::GenerateContentConfig>,
71 output_schema: Option<&serde_json::Value>,
72 cached_content: Option<&str>,
73) -> Option<adk_core::GenerateContentConfig> {
74 let mut config = base.cloned().unwrap_or_default();
75 if let Some(schema) = output_schema {
76 config.response_schema = Some(schema.clone());
77 }
78 if config.cached_content.is_none()
79 && let Some(cached_content) = cached_content
80 {
81 config.cached_content = Some(cached_content.to_string());
82 }
83
84 if base.is_some() || output_schema.is_some() || cached_content.is_some() {
85 Some(config)
86 } else {
87 None
88 }
89}
90
91fn collect_function_calls(content: &Content, invocation_id: &str) -> Vec<PendingToolCall> {
92 content
93 .parts
94 .iter()
95 .filter_map(|part| {
96 if let Part::FunctionCall { name, args, id, .. } = part {
97 Some((name, args, id))
98 } else {
99 None
100 }
101 })
102 .enumerate()
103 .map(|(index, (name, args, id))| PendingToolCall {
104 index,
105 name: name.clone(),
106 args: args.clone(),
107 id: id.clone(),
108 function_call_id: id
109 .clone()
110 .unwrap_or_else(|| format!("{invocation_id}_{name}_{index}")),
111 })
112 .collect()
113}
114
115fn build_partial_llm_event(
116 event_id: &str,
117 invocation_id: &str,
118 agent_name: &str,
119 request_json: &str,
120 chunk: &LlmResponse,
121 long_running_tool_ids: Vec<String>,
122) -> Event {
123 let mut event = Event::with_id(event_id, invocation_id);
124 event.author = agent_name.to_string();
125 event.llm_request = Some(request_json.to_string());
126 event
127 .provider_metadata
128 .insert("gcp.vertex.agent.llm_request".to_string(), request_json.to_string());
129 event.provider_metadata.insert(
130 "gcp.vertex.agent.llm_response".to_string(),
131 serde_json::to_string(chunk).unwrap_or_default(),
132 );
133 event.llm_response.partial = chunk.partial;
134 event.llm_response.turn_complete = chunk.turn_complete;
135 event.llm_response.finish_reason = chunk.finish_reason;
136 event.llm_response.usage_metadata = chunk.usage_metadata.clone();
137 event.llm_response.content = chunk.content.clone();
138 event.llm_response.provider_metadata = chunk.provider_metadata.clone();
139 event.llm_response.interaction_id = chunk.interaction_id.clone();
140 event.llm_response.interrupted = chunk.interrupted;
143 event.llm_response.error_code = chunk.error_code.clone();
144 event.llm_response.error_message = chunk.error_message.clone();
145 event.long_running_tool_ids = long_running_tool_ids;
146 event
147}
148
149fn build_final_llm_event(
150 event_id: &str,
151 invocation_id: &str,
152 agent_name: &str,
153 request_json: &str,
154 content: Option<&Content>,
155 last_chunk: Option<&LlmResponse>,
156 long_running_tool_ids: Vec<String>,
157) -> Event {
158 let mut event = Event::with_id(event_id, invocation_id);
159 event.author = agent_name.to_string();
160 event.llm_request = Some(request_json.to_string());
161 event
162 .provider_metadata
163 .insert("gcp.vertex.agent.llm_request".to_string(), request_json.to_string());
164 event.llm_response.content = content.cloned();
165 event.llm_response.partial = false;
166 event.llm_response.turn_complete = true;
167
168 if let Some(last_chunk) = last_chunk {
169 event.llm_response.finish_reason = last_chunk.finish_reason;
170 event.llm_response.usage_metadata = last_chunk.usage_metadata.clone();
171 event.llm_response.provider_metadata = last_chunk.provider_metadata.clone();
172 event.llm_response.interaction_id = last_chunk.interaction_id.clone();
173 event.llm_response.interrupted = last_chunk.interrupted;
174 event.llm_response.error_code = last_chunk.error_code.clone();
175 event.llm_response.error_message = last_chunk.error_message.clone();
176 event.provider_metadata.insert(
177 "gcp.vertex.agent.llm_response".to_string(),
178 serde_json::to_string(last_chunk).unwrap_or_default(),
179 );
180 }
181
182 event.long_running_tool_ids = long_running_tool_ids;
183 event
184}
185
186pub struct LlmAgent {
194 name: String,
195 description: String,
196 model: Arc<dyn Llm>,
197 instruction: Option<String>,
198 instruction_provider: Option<Arc<InstructionProvider>>,
199 global_instruction: Option<String>,
200 global_instruction_provider: Option<Arc<GlobalInstructionProvider>>,
201 skills_index: Option<Arc<SkillIndex>>,
202 skill_policy: SelectionPolicy,
203 max_skill_chars: usize,
204 #[allow(dead_code)] input_schema: Option<serde_json::Value>,
206 output_schema: Option<serde_json::Value>,
207 output_max_retries: usize,
209 disallow_transfer_to_parent: bool,
210 disallow_transfer_to_peers: bool,
211 include_contents: adk_core::IncludeContents,
212 tools: Vec<Arc<dyn Tool>>,
213 toolsets: Vec<Arc<dyn Toolset>>,
214 sub_agents: Vec<Arc<dyn Agent>>,
215 output_key: Option<String>,
216 generate_content_config: Option<adk_core::GenerateContentConfig>,
218 max_iterations: u32,
220 tool_timeout: std::time::Duration,
222 before_callbacks: Arc<Vec<BeforeAgentCallback>>,
223 after_callbacks: Arc<Vec<AfterAgentCallback>>,
224 before_model_callbacks: Arc<Vec<BeforeModelCallback>>,
225 after_model_callbacks: Arc<Vec<AfterModelCallback>>,
226 before_tool_callbacks: Arc<Vec<BeforeToolCallback>>,
227 after_tool_callbacks: Arc<Vec<AfterToolCallback>>,
228 on_tool_error_callbacks: Arc<Vec<OnToolErrorCallback>>,
229 after_tool_callbacks_full: Arc<Vec<AfterToolCallbackFull>>,
231 default_retry_budget: Option<RetryBudget>,
233 tool_retry_budgets: std::collections::HashMap<String, RetryBudget>,
235 circuit_breaker_threshold: Option<u32>,
238 tool_confirmation_policy: ToolConfirmationPolicy,
239 tool_execution_strategy: Option<ToolExecutionStrategy>,
242 input_guardrails: Arc<GuardrailSet>,
243 output_guardrails: Arc<GuardrailSet>,
244 #[cfg(feature = "enhanced-plugins")]
247 enhanced_plugin_manager: Option<Arc<EnhancedPluginManager>>,
248 #[cfg(feature = "sandbox")]
252 sandbox_config: Option<adk_sandbox::workspace::SandboxConfig>,
253}
254
255struct PromptConfig {
256 instruction: Option<String>,
257 instruction_provider: Option<Arc<InstructionProvider>>,
258 global_instruction: Option<String>,
259 global_instruction_provider: Option<Arc<GlobalInstructionProvider>>,
260 skills_index: Option<Arc<SkillIndex>>,
261 skill_policy: SelectionPolicy,
262 max_skill_chars: usize,
263 output_schema: Option<serde_json::Value>,
264 include_contents: adk_core::IncludeContents,
265}
266
267impl PromptConfig {
268 fn from_agent(agent: &LlmAgent) -> Self {
269 Self {
270 instruction: agent.instruction.clone(),
271 instruction_provider: agent.instruction_provider.clone(),
272 global_instruction: agent.global_instruction.clone(),
273 global_instruction_provider: agent.global_instruction_provider.clone(),
274 skills_index: agent.skills_index.clone(),
275 skill_policy: agent.skill_policy.clone(),
276 max_skill_chars: agent.max_skill_chars,
277 output_schema: agent.output_schema.clone(),
278 include_contents: agent.include_contents,
279 }
280 }
281
282 async fn prepare_conversation(
283 &self,
284 ctx: &Arc<dyn InvocationContext>,
285 agent_name: &str,
286 ) -> Result<Vec<Content>> {
287 let mut preamble = Vec::new();
288
289 if let Some(provider) = &self.global_instruction_provider {
290 let instruction = provider(ctx.clone() as Arc<dyn ReadonlyContext>).await?;
291 if !instruction.is_empty() {
292 preamble.push(Content::new("user").with_text(instruction));
293 }
294 } else if let Some(template) = &self.global_instruction {
295 let instruction = adk_core::inject_session_state(ctx.as_ref(), template).await?;
296 if !instruction.is_empty() {
297 preamble.push(Content::new("user").with_text(instruction));
298 }
299 }
300
301 if let Some(provider) = &self.instruction_provider {
302 let instruction = provider(ctx.clone() as Arc<dyn ReadonlyContext>).await?;
303 if !instruction.is_empty() {
304 preamble.push(Content::new("user").with_text(instruction));
305 }
306 } else if let Some(template) = &self.instruction {
307 let instruction = adk_core::inject_session_state(ctx.as_ref(), template).await?;
308 if !instruction.is_empty() {
309 preamble.push(Content::new("user").with_text(instruction));
310 }
311 }
312
313 if let Some(schema) = &self.output_schema {
314 preamble.push(Content::new("user").with_text(format!(
315 "You MUST respond with valid JSON conforming to this schema: {schema}. Do not include any text outside the JSON object."
316 )));
317 }
318
319 let agent_filter =
320 if ctx.run_config().transfer_targets.is_empty() { None } else { Some(agent_name) };
321 let mut session_history =
322 ctx.session().conversation_history_scoped(agent_filter, ctx.branch());
323 let mut current_user_content = ctx.user_content().clone();
324 if let Some(index) = &self.skills_index {
325 apply_skill_injection(
326 &mut current_user_content,
327 index.as_ref(),
328 &self.skill_policy,
329 self.max_skill_chars,
330 );
331 }
332 if let Some(index) = session_history.iter().rposition(|content| content.role == "user") {
333 session_history[index] = current_user_content.clone();
334 } else {
335 session_history.push(current_user_content.clone());
336 }
337
338 Ok(match self.include_contents {
339 adk_core::IncludeContents::None => {
340 preamble.push(current_user_content);
341 preamble
342 }
343 adk_core::IncludeContents::Default => {
344 preamble.extend(session_history);
345 preamble
346 }
347 })
348 }
349}
350
351struct ToolSetup {
352 tools: Vec<Arc<dyn Tool>>,
353 toolsets: Vec<Arc<dyn Toolset>>,
354 sub_agents: Vec<Arc<dyn Agent>>,
355 disallow_transfer_to_parent: bool,
356 disallow_transfer_to_peers: bool,
357}
358
359struct ResolvedTools {
360 map: HashMap<String, Arc<dyn Tool>>,
361 declarations: HashMap<String, serde_json::Value>,
362 transfer_targets: Vec<String>,
363}
364
365impl ToolSetup {
366 fn from_agent(agent: &LlmAgent) -> Self {
367 Self {
368 tools: agent.tools.clone(),
369 toolsets: agent.toolsets.clone(),
370 sub_agents: agent.sub_agents.clone(),
371 disallow_transfer_to_parent: agent.disallow_transfer_to_parent,
372 disallow_transfer_to_peers: agent.disallow_transfer_to_peers,
373 }
374 }
375
376 async fn resolve(&self, ctx: &Arc<dyn InvocationContext>) -> Result<ResolvedTools> {
377 let mut tools = self.tools.clone();
378 let static_tool_names: std::collections::HashSet<_> =
379 tools.iter().map(|tool| tool.name().to_string()).collect();
380 let mut toolset_sources = std::collections::HashMap::<String, String>::new();
381 let mut active_toolsets: Vec<&dyn Toolset> =
382 self.toolsets.iter().map(AsRef::as_ref).collect();
383 active_toolsets.extend(
384 ctx.run_config().runtime_toolsets.iter().map(|runtime| runtime.toolset().as_ref()),
385 );
386
387 for toolset in active_toolsets {
388 for tool in toolset.tools(ctx.clone() as Arc<dyn ReadonlyContext>).await? {
389 let name = tool.name().to_string();
390 if static_tool_names.contains(&name) {
391 return Err(adk_core::AdkError::agent(format!(
392 "Duplicate tool name '{name}': conflict between static tool and toolset '{}'",
393 toolset.name()
394 )));
395 }
396 if let Some(other_toolset) = toolset_sources.get(&name) {
397 return Err(adk_core::AdkError::agent(format!(
398 "Duplicate tool name '{name}': conflict between toolset '{other_toolset}' and toolset '{}'",
399 toolset.name()
400 )));
401 }
402 toolset_sources.insert(name, toolset.name().to_string());
403 tools.push(tool);
404 }
405 }
406
407 let map = tools.iter().map(|tool| (tool.name().to_string(), tool.clone())).collect();
408 let mut declarations = tools
409 .iter()
410 .map(|tool| (tool.name().to_string(), tool.declaration()))
411 .collect::<std::collections::HashMap<_, _>>();
412 let mut transfer_targets: Vec<String> =
413 self.sub_agents.iter().map(|agent| agent.name().to_string()).collect();
414 let child_names: std::collections::HashSet<_> =
415 self.sub_agents.iter().map(|agent| agent.name()).collect();
416 let parent_name = ctx.run_config().parent_agent.as_deref();
417
418 for target in &ctx.run_config().transfer_targets {
419 if child_names.contains(target.as_str()) {
420 continue;
421 }
422 let is_parent = parent_name == Some(target.as_str());
423 if (is_parent && self.disallow_transfer_to_parent)
424 || (!is_parent && self.disallow_transfer_to_peers)
425 {
426 continue;
427 }
428 transfer_targets.push(target.clone());
429 }
430
431 if !transfer_targets.is_empty() {
432 declarations.insert(
433 "transfer_to_agent".to_string(),
434 serde_json::json!({
435 "name": "transfer_to_agent",
436 "description": format!(
437 "Transfer execution to another agent. Valid targets: {}",
438 transfer_targets.join(", ")
439 ),
440 "parameters": {
441 "type": "object",
442 "properties": {
443 "agent_name": {
444 "type": "string",
445 "description": "The name of the agent to transfer to.",
446 "enum": transfer_targets
447 }
448 },
449 "required": ["agent_name"]
450 }
451 }),
452 );
453 }
454
455 Ok(ResolvedTools { map, declarations, transfer_targets })
456 }
457}
458
459impl std::fmt::Debug for LlmAgent {
460 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
461 f.debug_struct("LlmAgent")
462 .field("name", &self.name)
463 .field("description", &self.description)
464 .field("model", &self.model.name())
465 .field("instruction", &self.instruction)
466 .field("tools_count", &self.tools.len())
467 .field("sub_agents_count", &self.sub_agents.len())
468 .finish()
469 }
470}
471
472fn static_confirmation_decision(
480 decisions: &std::collections::HashMap<String, ToolConfirmationDecision>,
481 fingerprints: &std::collections::HashMap<String, String>,
482 function_call_id: &str,
483 tool_name: &str,
484 args: &serde_json::Value,
485) -> Option<ToolConfirmationDecision> {
486 let decision = decisions.get(function_call_id).copied()?;
487 if let Some(expected) = fingerprints.get(function_call_id) {
488 let actual = adk_core::tool_call_fingerprint(tool_name, args);
489 if &actual != expected {
490 tracing::warn!(
491 tool.name = %tool_name,
492 function_call.id = %function_call_id,
493 "confirmation decision does not match this call's arguments, treating as unconfirmed"
494 );
495 return None;
496 }
497 }
498 Some(decision)
499}
500
501impl LlmAgent {
502 #[cfg(feature = "sandbox")]
509 pub fn sandbox_config(&self) -> Option<&adk_sandbox::workspace::SandboxConfig> {
510 self.sandbox_config.as_ref()
511 }
512
513 async fn apply_input_guardrails(
514 ctx: Arc<dyn InvocationContext>,
515 input_guardrails: Arc<GuardrailSet>,
516 ) -> Result<Arc<dyn InvocationContext>> {
517 let content =
518 enforce_guardrails(input_guardrails.as_ref(), ctx.user_content(), "input").await?;
519 if content.role != ctx.user_content().role || content.parts != ctx.user_content().parts {
520 Ok(with_user_content_override(ctx, content))
521 } else {
522 Ok(ctx)
523 }
524 }
525
526 async fn apply_output_guardrails(
527 output_guardrails: &GuardrailSet,
528 content: Content,
529 ) -> Result<Content> {
530 enforce_guardrails(output_guardrails, &content, "output").await
531 }
532
533 fn history_parts_from_provider_metadata(
534 provider_metadata: Option<&serde_json::Value>,
535 ) -> Vec<Part> {
536 let Some(provider_metadata) = provider_metadata else {
537 return Vec::new();
538 };
539
540 let history_parts = provider_metadata
541 .get("conversation_history_parts")
542 .or_else(|| {
543 provider_metadata
544 .get("openai")
545 .and_then(|openai| openai.get("conversation_history_parts"))
546 })
547 .and_then(serde_json::Value::as_array);
548
549 history_parts
550 .into_iter()
551 .flatten()
552 .filter_map(|value| serde_json::from_value::<Part>(value.clone()).ok())
553 .collect()
554 }
555
556 fn augment_content_for_history(
557 content: &Content,
558 provider_metadata: Option<&serde_json::Value>,
559 ) -> Content {
560 let mut augmented = content.clone();
561 augmented.parts.extend(Self::history_parts_from_provider_metadata(provider_metadata));
562 augmented
563 }
564}
565
566fn validate_output_against_schema(
571 text: &str,
572 schema: &serde_json::Value,
573) -> std::result::Result<serde_json::Value, String> {
574 let parsed: serde_json::Value =
575 serde_json::from_str(text).map_err(|e| format!("Response is not valid JSON: {e}"))?;
576
577 let validator =
578 jsonschema::validator_for(schema).map_err(|e| format!("Invalid schema: {e}"))?;
579
580 let errors: Vec<String> = validator.iter_errors(&parsed).map(|e| e.to_string()).collect();
581
582 if errors.is_empty() { Ok(parsed) } else { Err(errors.join("; ")) }
583}
584
585fn extract_text_from_events(events: &[Event]) -> Option<String> {
590 for event in events.iter().rev() {
591 if let Some(ref content) = event.llm_response.content {
592 let text: String =
593 content
594 .parts
595 .iter()
596 .filter_map(|p| {
597 if let Part::Text { text } = p { Some(text.as_str()) } else { None }
598 })
599 .collect::<Vec<_>>()
600 .join("");
601 if !text.is_empty() {
602 return Some(text);
603 }
604 }
605 }
606 None
607}
608
609pub fn extract_typed<T: serde::de::DeserializeOwned>(events: &[Event]) -> Result<T> {
631 let text = extract_text_from_events(events).ok_or_else(|| {
632 adk_core::AdkError::agent("no text content found in events for typed extraction")
633 })?;
634
635 serde_json::from_str(&text)
636 .map_err(|e| adk_core::AdkError::agent(format!("output deserialization failed: {e}")))
637}
638
639pub struct LlmAgentBuilder {
641 name: String,
642 description: Option<String>,
643 model: Option<Arc<dyn Llm>>,
644 instruction: Option<String>,
645 instruction_provider: Option<Arc<InstructionProvider>>,
646 global_instruction: Option<String>,
647 global_instruction_provider: Option<Arc<GlobalInstructionProvider>>,
648 skills_index: Option<Arc<SkillIndex>>,
649 skill_policy: SelectionPolicy,
650 max_skill_chars: usize,
651 input_schema: Option<serde_json::Value>,
652 output_schema: Option<serde_json::Value>,
653 output_max_retries: usize,
654 disallow_transfer_to_parent: bool,
655 disallow_transfer_to_peers: bool,
656 include_contents: adk_core::IncludeContents,
657 tools: Vec<Arc<dyn Tool>>,
658 toolsets: Vec<Arc<dyn Toolset>>,
659 sub_agents: Vec<Arc<dyn Agent>>,
660 output_key: Option<String>,
661 generate_content_config: Option<adk_core::GenerateContentConfig>,
662 max_iterations: u32,
663 tool_timeout: std::time::Duration,
664 before_callbacks: Vec<BeforeAgentCallback>,
665 after_callbacks: Vec<AfterAgentCallback>,
666 before_model_callbacks: Vec<BeforeModelCallback>,
667 after_model_callbacks: Vec<AfterModelCallback>,
668 before_tool_callbacks: Vec<BeforeToolCallback>,
669 after_tool_callbacks: Vec<AfterToolCallback>,
670 on_tool_error_callbacks: Vec<OnToolErrorCallback>,
671 after_tool_callbacks_full: Vec<AfterToolCallbackFull>,
672 default_retry_budget: Option<RetryBudget>,
673 tool_retry_budgets: std::collections::HashMap<String, RetryBudget>,
674 circuit_breaker_threshold: Option<u32>,
675 tool_confirmation_policy: ToolConfirmationPolicy,
676 tool_execution_strategy: Option<ToolExecutionStrategy>,
677 input_guardrails: GuardrailSet,
678 output_guardrails: GuardrailSet,
679 #[cfg(feature = "enhanced-plugins")]
681 enhanced_plugins: Vec<Arc<dyn EnhancedPlugin>>,
682 #[cfg(feature = "sandbox")]
684 sandbox_config: Option<adk_sandbox::workspace::SandboxConfig>,
685}
686
687impl LlmAgentBuilder {
688 pub fn new(name: impl Into<String>) -> Self {
690 Self {
691 name: name.into(),
692 description: None,
693 model: None,
694 instruction: None,
695 instruction_provider: None,
696 global_instruction: None,
697 global_instruction_provider: None,
698 skills_index: None,
699 skill_policy: SelectionPolicy::default(),
700 max_skill_chars: 2000,
701 input_schema: None,
702 output_schema: None,
703 output_max_retries: 3,
704 disallow_transfer_to_parent: false,
705 disallow_transfer_to_peers: false,
706 include_contents: adk_core::IncludeContents::Default,
707 tools: Vec::new(),
708 toolsets: Vec::new(),
709 sub_agents: Vec::new(),
710 output_key: None,
711 generate_content_config: None,
712 max_iterations: DEFAULT_MAX_ITERATIONS,
713 tool_timeout: DEFAULT_TOOL_TIMEOUT,
714 before_callbacks: Vec::new(),
715 after_callbacks: Vec::new(),
716 before_model_callbacks: Vec::new(),
717 after_model_callbacks: Vec::new(),
718 before_tool_callbacks: Vec::new(),
719 after_tool_callbacks: Vec::new(),
720 on_tool_error_callbacks: Vec::new(),
721 after_tool_callbacks_full: Vec::new(),
722 default_retry_budget: None,
723 tool_retry_budgets: std::collections::HashMap::new(),
724 circuit_breaker_threshold: None,
725 tool_confirmation_policy: ToolConfirmationPolicy::Never,
726 tool_execution_strategy: None,
727 input_guardrails: GuardrailSet::new(),
728 output_guardrails: GuardrailSet::new(),
729 #[cfg(feature = "enhanced-plugins")]
730 enhanced_plugins: Vec::new(),
731 #[cfg(feature = "sandbox")]
732 sandbox_config: None,
733 }
734 }
735
736 pub fn description(mut self, desc: impl Into<String>) -> Self {
738 self.description = Some(desc.into());
739 self
740 }
741
742 pub fn model(mut self, model: Arc<dyn Llm>) -> Self {
744 self.model = Some(model);
745 self
746 }
747
748 pub fn instruction(mut self, instruction: impl Into<String>) -> Self {
750 self.instruction = Some(instruction.into());
751 self
752 }
753
754 pub fn instruction_provider(mut self, provider: InstructionProvider) -> Self {
756 self.instruction_provider = Some(Arc::new(provider));
757 self
758 }
759
760 pub fn global_instruction(mut self, instruction: impl Into<String>) -> Self {
762 self.global_instruction = Some(instruction.into());
763 self
764 }
765
766 pub fn global_instruction_provider(mut self, provider: GlobalInstructionProvider) -> Self {
768 self.global_instruction_provider = Some(Arc::new(provider));
769 self
770 }
771
772 #[cfg(feature = "skills")]
777 pub fn with_skills(mut self, index: SkillIndex) -> Self {
778 self.skills_index = Some(Arc::new(index));
779 self
780 }
781
782 #[cfg(feature = "skills")]
784 pub fn with_auto_skills(self) -> Result<Self> {
785 self.with_skills_from_root(".")
786 }
787
788 #[cfg(feature = "skills")]
790 pub fn with_skills_from_root(mut self, root: impl AsRef<std::path::Path>) -> Result<Self> {
791 let index = load_skill_index(root).map_err(|e| adk_core::AdkError::agent(e.to_string()))?;
792 self.skills_index = Some(Arc::new(index));
793 Ok(self)
794 }
795
796 #[cfg(feature = "skills")]
798 pub fn with_skill_policy(mut self, policy: SelectionPolicy) -> Self {
799 self.skill_policy = policy;
800 self
801 }
802
803 #[cfg(feature = "skills")]
805 pub fn with_skill_budget(mut self, max_chars: usize) -> Self {
806 self.max_skill_chars = max_chars;
807 self
808 }
809
810 pub fn input_schema(mut self, schema: serde_json::Value) -> Self {
812 self.input_schema = Some(schema);
813 self
814 }
815
816 pub fn output_schema(mut self, schema: serde_json::Value) -> Self {
818 self.output_schema = Some(schema);
819 self
820 }
821
822 pub fn output_type<T: schemars::JsonSchema>(mut self) -> Self {
845 let schema = schemars::schema_for!(T);
846 self.output_schema =
847 Some(serde_json::to_value(schema).expect("schema serialization cannot fail"));
848 self
849 }
850
851 pub fn output_max_retries(mut self, n: usize) -> Self {
856 self.output_max_retries = n;
857 self
858 }
859
860 pub fn disallow_transfer_to_parent(mut self, disallow: bool) -> Self {
862 self.disallow_transfer_to_parent = disallow;
863 self
864 }
865
866 pub fn disallow_transfer_to_peers(mut self, disallow: bool) -> Self {
868 self.disallow_transfer_to_peers = disallow;
869 self
870 }
871
872 pub fn include_contents(mut self, include: adk_core::IncludeContents) -> Self {
874 self.include_contents = include;
875 self
876 }
877
878 pub fn output_key(mut self, key: impl Into<String>) -> Self {
880 self.output_key = Some(key.into());
881 self
882 }
883
884 pub fn generate_content_config(mut self, config: adk_core::GenerateContentConfig) -> Self {
905 self.generate_content_config = Some(config);
906 self
907 }
908
909 pub fn temperature(mut self, temperature: f32) -> Self {
912 self.generate_content_config
913 .get_or_insert(adk_core::GenerateContentConfig::default())
914 .temperature = Some(temperature);
915 self
916 }
917
918 pub fn top_p(mut self, top_p: f32) -> Self {
920 self.generate_content_config
921 .get_or_insert(adk_core::GenerateContentConfig::default())
922 .top_p = Some(top_p);
923 self
924 }
925
926 pub fn top_k(mut self, top_k: i32) -> Self {
928 self.generate_content_config
929 .get_or_insert(adk_core::GenerateContentConfig::default())
930 .top_k = Some(top_k);
931 self
932 }
933
934 pub fn max_output_tokens(mut self, max_tokens: i32) -> Self {
936 self.generate_content_config
937 .get_or_insert(adk_core::GenerateContentConfig::default())
938 .max_output_tokens = Some(max_tokens);
939 self
940 }
941
942 pub fn max_iterations(mut self, max: u32) -> Self {
945 self.max_iterations = max;
946 self
947 }
948
949 pub fn tool_timeout(mut self, timeout: std::time::Duration) -> Self {
952 self.tool_timeout = timeout;
953 self
954 }
955
956 pub fn tool(mut self, tool: Arc<dyn Tool>) -> Self {
958 self.tools.push(tool);
959 self
960 }
961
962 pub fn toolset(mut self, toolset: Arc<dyn Toolset>) -> Self {
968 self.toolsets.push(toolset);
969 self
970 }
971
972 pub fn sub_agent(mut self, agent: Arc<dyn Agent>) -> Self {
974 self.sub_agents.push(agent);
975 self
976 }
977
978 pub fn before_callback(mut self, callback: BeforeAgentCallback) -> Self {
980 self.before_callbacks.push(callback);
981 self
982 }
983
984 pub fn after_callback(mut self, callback: AfterAgentCallback) -> Self {
986 self.after_callbacks.push(callback);
987 self
988 }
989
990 pub fn before_model_callback(mut self, callback: BeforeModelCallback) -> Self {
992 self.before_model_callbacks.push(callback);
993 self
994 }
995
996 pub fn after_model_callback(mut self, callback: AfterModelCallback) -> Self {
998 self.after_model_callbacks.push(callback);
999 self
1000 }
1001
1002 pub fn before_tool_callback(mut self, callback: BeforeToolCallback) -> Self {
1004 self.before_tool_callbacks.push(callback);
1005 self
1006 }
1007
1008 pub fn after_tool_callback(mut self, callback: AfterToolCallback) -> Self {
1010 self.after_tool_callbacks.push(callback);
1011 self
1012 }
1013
1014 pub fn after_tool_callback_full(mut self, callback: AfterToolCallbackFull) -> Self {
1029 self.after_tool_callbacks_full.push(callback);
1030 self
1031 }
1032
1033 pub fn on_tool_error(mut self, callback: OnToolErrorCallback) -> Self {
1041 self.on_tool_error_callbacks.push(callback);
1042 self
1043 }
1044
1045 pub fn default_retry_budget(mut self, budget: RetryBudget) -> Self {
1052 self.default_retry_budget = Some(budget);
1053 self
1054 }
1055
1056 pub fn tool_retry_budget(mut self, tool_name: impl Into<String>, budget: RetryBudget) -> Self {
1061 self.tool_retry_budgets.insert(tool_name.into(), budget);
1062 self
1063 }
1064
1065 pub fn circuit_breaker_threshold(mut self, threshold: u32) -> Self {
1072 self.circuit_breaker_threshold = Some(threshold);
1073 self
1074 }
1075
1076 pub fn tool_confirmation_policy(mut self, policy: ToolConfirmationPolicy) -> Self {
1078 self.tool_confirmation_policy = policy;
1079 self
1080 }
1081
1082 pub fn require_tool_confirmation(mut self, tool_name: impl Into<String>) -> Self {
1084 self.tool_confirmation_policy = self.tool_confirmation_policy.with_tool(tool_name);
1085 self
1086 }
1087
1088 pub fn require_tool_confirmation_for_all(mut self) -> Self {
1090 self.tool_confirmation_policy = ToolConfirmationPolicy::Always;
1091 self
1092 }
1093
1094 pub fn tool_execution_strategy(mut self, strategy: ToolExecutionStrategy) -> Self {
1102 self.tool_execution_strategy = Some(strategy);
1103 self
1104 }
1105
1106 pub fn input_guardrails(mut self, guardrails: GuardrailSet) -> Self {
1115 self.input_guardrails = guardrails;
1116 self
1117 }
1118
1119 pub fn output_guardrails(mut self, guardrails: GuardrailSet) -> Self {
1128 self.output_guardrails = guardrails;
1129 self
1130 }
1131
1132 #[cfg(feature = "enhanced-plugins")]
1152 pub fn enhanced_plugin(mut self, plugin: Arc<dyn EnhancedPlugin>) -> Self {
1153 self.enhanced_plugins.push(plugin);
1154 self
1155 }
1156
1157 #[cfg(feature = "enhanced-plugins")]
1179 pub fn enhanced_plugins(mut self, plugins: Vec<Arc<dyn EnhancedPlugin>>) -> Self {
1180 self.enhanced_plugins.extend(plugins);
1181 self
1182 }
1183
1184 #[cfg(feature = "sandbox")]
1219 pub fn sandbox_config(mut self, config: adk_sandbox::workspace::SandboxConfig) -> Self {
1220 self.sandbox_config = Some(config);
1221 self
1222 }
1223
1224 pub fn build(self) -> Result<LlmAgent> {
1226 let model = self.model.ok_or_else(|| adk_core::AdkError::agent("Model is required"))?;
1227
1228 let mut seen_names = std::collections::HashSet::new();
1229 for agent in &self.sub_agents {
1230 if !seen_names.insert(agent.name()) {
1231 return Err(adk_core::AdkError::agent(format!(
1232 "Duplicate sub-agent name: {}",
1233 agent.name()
1234 )));
1235 }
1236 }
1237
1238 #[cfg(feature = "sandbox")]
1241 if let Some(ref sandbox_cfg) = self.sandbox_config {
1242 use adk_sandbox::workspace::Capability;
1243 if model.uses_interactions_api()
1244 && (sandbox_cfg.capabilities.contains(&Capability::Shell)
1245 || sandbox_cfg.capabilities.contains(&Capability::Filesystem))
1246 {
1247 return Err(adk_core::AdkError::new(
1248 adk_core::ErrorComponent::Agent,
1249 adk_core::ErrorCategory::InvalidInput,
1250 "code.gemini_interactions_conflict",
1251 "Cannot combine Gemini Interactions API (server-managed environment) \
1252 with client-side sandbox tools (Shell/Filesystem). These provide \
1253 competing filesystems and would produce nondeterministic behavior. \
1254 Either disable use_interactions_api or remove sandbox capabilities.",
1255 ));
1256 }
1257 }
1258
1259 #[cfg(feature = "enhanced-plugins")]
1261 let enhanced_plugin_manager = if self.enhanced_plugins.is_empty() {
1262 None
1263 } else {
1264 Some(Arc::new(EnhancedPluginManager::new(self.enhanced_plugins)))
1265 };
1266
1267 Ok(LlmAgent {
1268 name: self.name,
1269 description: self.description.unwrap_or_default(),
1270 model,
1271 instruction: self.instruction,
1272 instruction_provider: self.instruction_provider,
1273 global_instruction: self.global_instruction,
1274 global_instruction_provider: self.global_instruction_provider,
1275 skills_index: self.skills_index,
1276 skill_policy: self.skill_policy,
1277 max_skill_chars: self.max_skill_chars,
1278 input_schema: self.input_schema,
1279 output_schema: self.output_schema,
1280 output_max_retries: self.output_max_retries,
1281 disallow_transfer_to_parent: self.disallow_transfer_to_parent,
1282 disallow_transfer_to_peers: self.disallow_transfer_to_peers,
1283 include_contents: self.include_contents,
1284 tools: self.tools,
1285 toolsets: self.toolsets,
1286 sub_agents: self.sub_agents,
1287 output_key: self.output_key,
1288 generate_content_config: self.generate_content_config,
1289 max_iterations: self.max_iterations,
1290 tool_timeout: self.tool_timeout,
1291 before_callbacks: Arc::new(self.before_callbacks),
1292 after_callbacks: Arc::new(self.after_callbacks),
1293 before_model_callbacks: Arc::new(self.before_model_callbacks),
1294 after_model_callbacks: Arc::new(self.after_model_callbacks),
1295 before_tool_callbacks: Arc::new(self.before_tool_callbacks),
1296 after_tool_callbacks: Arc::new(self.after_tool_callbacks),
1297 on_tool_error_callbacks: Arc::new(self.on_tool_error_callbacks),
1298 after_tool_callbacks_full: Arc::new(self.after_tool_callbacks_full),
1299 default_retry_budget: self.default_retry_budget,
1300 tool_retry_budgets: self.tool_retry_budgets,
1301 circuit_breaker_threshold: self.circuit_breaker_threshold,
1302 tool_confirmation_policy: self.tool_confirmation_policy,
1303 tool_execution_strategy: self.tool_execution_strategy,
1304 input_guardrails: Arc::new(self.input_guardrails),
1305 output_guardrails: Arc::new(self.output_guardrails),
1306 #[cfg(feature = "enhanced-plugins")]
1307 enhanced_plugin_manager,
1308 #[cfg(feature = "sandbox")]
1309 sandbox_config: self.sandbox_config,
1310 })
1311 }
1312}
1313
1314const TOOL_PROGRESS_CAPACITY: usize = 256;
1322
1323const TOOL_PROGRESS_SEND_TIMEOUT: std::time::Duration = std::time::Duration::from_millis(100);
1328
1329const TOOL_PROGRESS_MAX_CHUNK_BYTES: usize = 8 * 1024;
1331
1332const TOOL_PROGRESS_MAX_TOTAL_BYTES: usize = 1024 * 1024;
1334
1335const TOOL_PROGRESS_TRUNCATION_MARKER: &str = "[adk: tool progress truncated]";
1337
1338fn truncate_on_char_boundary(text: &str, max_bytes: usize) -> &str {
1340 if text.len() <= max_bytes {
1341 return text;
1342 }
1343 let mut end = max_bytes;
1344 while end > 0 && !text.is_char_boundary(end) {
1345 end -= 1;
1346 }
1347 &text[..end]
1348}
1349
1350struct AgentToolContext {
1351 parent_ctx: Arc<dyn InvocationContext>,
1352 function_call_id: String,
1353 tool_name: Option<String>,
1356 actions: Mutex<EventActions>,
1357 progress_tx: Option<tokio::sync::mpsc::Sender<Event>>,
1358 progress_bytes: std::sync::atomic::AtomicUsize,
1360 progress_truncated: std::sync::atomic::AtomicBool,
1362}
1363
1364impl AgentToolContext {
1365 fn new(parent_ctx: Arc<dyn InvocationContext>, function_call_id: String) -> Self {
1366 Self {
1367 parent_ctx,
1368 function_call_id,
1369 tool_name: None,
1370 actions: Mutex::new(EventActions::default()),
1371 progress_tx: None,
1372 progress_bytes: std::sync::atomic::AtomicUsize::new(0),
1373 progress_truncated: std::sync::atomic::AtomicBool::new(false),
1374 }
1375 }
1376
1377 fn with_tool_name(mut self, tool_name: impl Into<String>) -> Self {
1379 self.tool_name = Some(tool_name.into());
1380 self
1381 }
1382
1383 fn with_progress(mut self, tx: tokio::sync::mpsc::Sender<Event>) -> Self {
1386 self.progress_tx = Some(tx);
1387 self
1388 }
1389
1390 async fn request_secret(&self, name: &str, purpose: Option<&str>) -> Result<Option<String>> {
1395 let mut request = adk_core::SecretRequest::new(name)
1396 .with_identity(
1397 self.parent_ctx.app_name(),
1398 self.parent_ctx.user_id(),
1399 self.parent_ctx.session_id(),
1400 )
1401 .with_invocation_id(self.parent_ctx.invocation_id());
1402 if let Some(tool_name) = &self.tool_name {
1403 request = request.with_tool_name(tool_name);
1404 }
1405 if let Some(purpose) = purpose {
1406 request = request.with_purpose(purpose);
1407 }
1408 self.parent_ctx.get_secret_for(&request).await
1409 }
1410
1411 async fn forward_progress(
1418 &self,
1419 tx: &tokio::sync::mpsc::Sender<Event>,
1420 stream: &str,
1421 chunk: &str,
1422 ) {
1423 use std::sync::atomic::Ordering;
1424
1425 if self.progress_truncated.load(Ordering::Relaxed) {
1426 return;
1427 }
1428
1429 let payload = truncate_on_char_boundary(chunk, TOOL_PROGRESS_MAX_CHUNK_BYTES);
1432 let forwarded = self.progress_bytes.fetch_add(payload.len(), Ordering::Relaxed);
1433 if forwarded.saturating_add(payload.len()) > TOOL_PROGRESS_MAX_TOTAL_BYTES {
1434 self.mark_progress_truncated(tx, stream).await;
1435 return;
1436 }
1437
1438 let event = Event::tool_progress(
1439 self.parent_ctx.invocation_id(),
1440 self.parent_ctx.agent_name(),
1441 &self.function_call_id,
1442 stream,
1443 payload,
1444 );
1445
1446 match tx.try_send(event) {
1447 Ok(()) => {}
1448 Err(tokio::sync::mpsc::error::TrySendError::Full(event)) => {
1449 match tokio::time::timeout(TOOL_PROGRESS_SEND_TIMEOUT, tx.send(event)).await {
1452 Ok(Ok(())) => {}
1453 Ok(Err(_)) => {}
1454 Err(_) => self.mark_progress_truncated(tx, stream).await,
1455 }
1456 }
1457 Err(tokio::sync::mpsc::error::TrySendError::Closed(_)) => {}
1458 }
1459 }
1460
1461 async fn mark_progress_truncated(&self, tx: &tokio::sync::mpsc::Sender<Event>, stream: &str) {
1463 use std::sync::atomic::Ordering;
1464 if self.progress_truncated.swap(true, Ordering::Relaxed) {
1465 return;
1466 }
1467 tracing::warn!(
1468 function_call.id = %self.function_call_id,
1469 progress.stream = %stream,
1470 "tool progress exceeded its budget, remaining output is not forwarded"
1471 );
1472 let marker = Event::tool_progress(
1473 self.parent_ctx.invocation_id(),
1474 self.parent_ctx.agent_name(),
1475 &self.function_call_id,
1476 stream,
1477 TOOL_PROGRESS_TRUNCATION_MARKER,
1478 );
1479 let _ = tx.try_send(marker);
1480 }
1481
1482 fn actions_guard(&self) -> std::sync::MutexGuard<'_, EventActions> {
1483 self.actions.lock().unwrap_or_else(|e| e.into_inner())
1484 }
1485}
1486
1487#[async_trait]
1488impl ReadonlyContext for AgentToolContext {
1489 fn invocation_id(&self) -> &str {
1490 self.parent_ctx.invocation_id()
1491 }
1492
1493 fn agent_name(&self) -> &str {
1494 self.parent_ctx.agent_name()
1495 }
1496
1497 fn user_id(&self) -> &str {
1498 self.parent_ctx.user_id()
1500 }
1501
1502 fn app_name(&self) -> &str {
1503 self.parent_ctx.app_name()
1505 }
1506
1507 fn session_id(&self) -> &str {
1508 self.parent_ctx.session_id()
1510 }
1511
1512 fn branch(&self) -> &str {
1513 self.parent_ctx.branch()
1514 }
1515
1516 fn user_content(&self) -> &Content {
1517 self.parent_ctx.user_content()
1518 }
1519}
1520
1521#[async_trait]
1522impl CallbackContext for AgentToolContext {
1523 fn artifacts(&self) -> Option<Arc<dyn adk_core::Artifacts>> {
1524 self.parent_ctx.artifacts()
1526 }
1527
1528 fn shared_state(&self) -> Option<Arc<adk_core::SharedState>> {
1529 self.parent_ctx.shared_state()
1530 }
1531}
1532
1533#[async_trait]
1534impl ToolContext for AgentToolContext {
1535 fn function_call_id(&self) -> &str {
1536 &self.function_call_id
1537 }
1538
1539 fn actions(&self) -> EventActions {
1540 self.actions_guard().clone()
1541 }
1542
1543 fn set_actions(&self, actions: EventActions) {
1544 *self.actions_guard() = actions;
1545 }
1546
1547 async fn search_memory(&self, query: &str) -> Result<Vec<MemoryEntry>> {
1548 if let Some(memory) = self.parent_ctx.memory() {
1550 memory.search(query).await
1551 } else {
1552 Ok(vec![])
1553 }
1554 }
1555
1556 fn user_scopes(&self) -> Vec<String> {
1557 self.parent_ctx.user_scopes()
1558 }
1559
1560 async fn get_secret(&self, name: &str) -> Result<Option<String>> {
1561 self.request_secret(name, None).await
1562 }
1563
1564 async fn get_secret_for_purpose(&self, name: &str, purpose: &str) -> Result<Option<String>> {
1565 self.request_secret(name, Some(purpose)).await
1566 }
1567
1568 async fn emit_progress(&self, stream: &str, chunk: &str) {
1569 if let Some(tx) = &self.progress_tx {
1572 if !tx.is_closed() {
1574 self.forward_progress(tx, stream, chunk).await;
1575 }
1576 }
1577 tracing::debug!(
1579 target: "adk_agent::tool_progress",
1580 tool_call_id = %self.function_call_id,
1581 stream = %stream,
1582 "{chunk}",
1583 );
1584 }
1585}
1586
1587struct ToolOutcomeCallbackContext {
1591 inner: Arc<dyn CallbackContext>,
1592 outcome: ToolOutcome,
1593}
1594
1595#[async_trait]
1596impl ReadonlyContext for ToolOutcomeCallbackContext {
1597 fn invocation_id(&self) -> &str {
1598 self.inner.invocation_id()
1599 }
1600
1601 fn agent_name(&self) -> &str {
1602 self.inner.agent_name()
1603 }
1604
1605 fn user_id(&self) -> &str {
1606 self.inner.user_id()
1607 }
1608
1609 fn app_name(&self) -> &str {
1610 self.inner.app_name()
1611 }
1612
1613 fn session_id(&self) -> &str {
1614 self.inner.session_id()
1615 }
1616
1617 fn branch(&self) -> &str {
1618 self.inner.branch()
1619 }
1620
1621 fn user_content(&self) -> &Content {
1622 self.inner.user_content()
1623 }
1624}
1625
1626#[async_trait]
1627impl CallbackContext for ToolOutcomeCallbackContext {
1628 fn artifacts(&self) -> Option<Arc<dyn adk_core::Artifacts>> {
1629 self.inner.artifacts()
1630 }
1631
1632 fn tool_outcome(&self) -> Option<ToolOutcome> {
1633 Some(self.outcome.clone())
1634 }
1635}
1636
1637struct CircuitBreakerState {
1647 threshold: u32,
1648 failures: std::collections::HashMap<String, u32>,
1650}
1651
1652impl CircuitBreakerState {
1653 fn new(threshold: u32) -> Self {
1654 Self { threshold, failures: std::collections::HashMap::new() }
1655 }
1656
1657 fn is_open(&self, tool_name: &str) -> bool {
1659 self.failures.get(tool_name).copied().unwrap_or(0) >= self.threshold
1660 }
1661
1662 fn record(&mut self, outcome: &ToolOutcome) {
1664 if outcome.success {
1665 self.failures.remove(&outcome.tool_name);
1666 } else {
1667 let count = self.failures.entry(outcome.tool_name.clone()).or_insert(0);
1668 *count += 1;
1669 }
1670 }
1671}
1672
1673struct ToolExecutionResult {
1674 index: usize,
1675 content: Content,
1676 actions: EventActions,
1677 escalate_or_skip: bool,
1678}
1679
1680struct ToolExecutor<'a> {
1681 ctx: Arc<dyn InvocationContext>,
1682 tool_map: &'a std::collections::HashMap<String, Arc<dyn Tool>>,
1683 tool_retry_budgets: &'a std::collections::HashMap<String, RetryBudget>,
1684 default_retry_budget: &'a Option<RetryBudget>,
1685 before_tool_callbacks: &'a Arc<Vec<BeforeToolCallback>>,
1686 after_tool_callbacks: &'a Arc<Vec<AfterToolCallback>>,
1687 after_tool_callbacks_full: &'a Arc<Vec<AfterToolCallbackFull>>,
1688 on_tool_error_callbacks: &'a Arc<Vec<OnToolErrorCallback>>,
1689 tool_confirmation_policy: &'a ToolConfirmationPolicy,
1690 cb_mutex: &'a std::sync::Mutex<Option<CircuitBreakerState>>,
1691 invocation_id: &'a str,
1692 concurrency_manager: &'a adk_core::ToolConcurrencyManager,
1693 progress_tx: tokio::sync::mpsc::Sender<Event>,
1694 tool_timeout: std::time::Duration,
1695 confirmation_decisions: &'a std::collections::HashMap<String, ToolConfirmationDecision>,
1696 confirmation_fingerprints: &'a std::collections::HashMap<String, String>,
1697 live_confirmation_decisions: &'a std::collections::HashMap<String, ToolConfirmationDecision>,
1698 #[cfg(feature = "enhanced-plugins")]
1699 enhanced_plugin_manager: &'a Option<Arc<EnhancedPluginManager>>,
1700}
1701
1702impl ToolExecutor<'_> {
1703 async fn execute(&self, call: PendingToolCall) -> ToolExecutionResult {
1704 let PendingToolCall { index, name, args, id, function_call_id } = call;
1705 let mut tool_actions = EventActions::default();
1706 let mut response_content: Option<Content> = None;
1707 let mut run_after_tool_callbacks = true;
1708 let mut tool_outcome_for_callback: Option<ToolOutcome> = None;
1709 let mut executed_tool: Option<Arc<dyn Tool>> = None;
1710 let mut executed_tool_response: Option<serde_json::Value> = None;
1711
1712 let _concurrency_permit = match self.concurrency_manager.acquire(&name).await {
1716 Ok(permit) => Some(permit),
1717 Err(e) => {
1718 let error_content = Content {
1720 role: "function".to_string(),
1721 parts: vec![Part::FunctionResponse {
1722 function_response: FunctionResponseData::new(
1723 name.clone(),
1724 serde_json::json!({ "error": e.to_string() }),
1725 ),
1726 id: id.clone(),
1727 annotations: None,
1728 }],
1729 };
1730 return ToolExecutionResult {
1731 index,
1732 content: error_content,
1733 actions: tool_actions,
1734 escalate_or_skip: false,
1735 };
1736 }
1737 };
1738
1739 if self.tool_confirmation_policy.requires_confirmation(&name) {
1741 match self.live_confirmation_decisions.get(&function_call_id).copied().or_else(|| {
1742 static_confirmation_decision(
1743 self.confirmation_decisions,
1744 self.confirmation_fingerprints,
1745 &function_call_id,
1746 &name,
1747 &args,
1748 )
1749 }) {
1750 Some(ToolConfirmationDecision::Approve) => {
1751 tool_actions.tool_confirmation_decision =
1752 Some(ToolConfirmationDecision::Approve);
1753 }
1754 Some(ToolConfirmationDecision::Deny) => {
1755 tool_actions.tool_confirmation_decision = Some(ToolConfirmationDecision::Deny);
1756 response_content = Some(Content {
1757 role: "function".to_string(),
1758 parts: vec![Part::FunctionResponse {
1759 function_response: FunctionResponseData::new(
1760 name.clone(),
1761 serde_json::json!({
1762 "error": format!("Tool '{}' execution denied by confirmation policy", name)
1763 }),
1764 ),
1765 id: id.clone(),
1766 annotations: None,
1767 }],
1768 });
1769 run_after_tool_callbacks = false;
1770 }
1771 None => {
1772 response_content = Some(Content {
1773 role: "function".to_string(),
1774 parts: vec![Part::FunctionResponse {
1775 function_response: FunctionResponseData::new(
1776 name.clone(),
1777 serde_json::json!({
1778 "error": format!("Tool '{}' requires confirmation", name)
1779 }),
1780 ),
1781 id: id.clone(),
1782 annotations: None,
1783 }],
1784 });
1785 run_after_tool_callbacks = false;
1786 }
1787 }
1788 }
1789
1790 #[allow(unused_mut)]
1793 let mut final_args = args.clone();
1794
1795 #[cfg(feature = "enhanced-plugins")]
1797 if response_content.is_none()
1798 && let Some(epm) = self.enhanced_plugin_manager.as_ref()
1799 && let Some(tool_ref) = self.tool_map.get(&name)
1800 {
1801 match epm
1802 .run_before_tool_call(
1803 tool_ref.clone(),
1804 final_args.clone(),
1805 self.ctx.clone() as Arc<dyn CallbackContext>,
1806 )
1807 .await
1808 {
1809 Ok(BeforeToolCallResult::Continue(modified_args)) => {
1810 final_args = modified_args;
1811 }
1812 Ok(BeforeToolCallResult::ShortCircuit(synthetic_result)) => {
1813 response_content = Some(Content {
1815 role: "function".to_string(),
1816 parts: vec![Part::FunctionResponse {
1817 function_response: FunctionResponseData::from_tool_result(
1818 name.clone(),
1819 synthetic_result,
1820 ),
1821 id: id.clone(),
1822 annotations: None,
1823 }],
1824 });
1825 executed_tool = Some(tool_ref.clone());
1826 }
1827 Err(e) => {
1828 response_content = Some(Content {
1829 role: "function".to_string(),
1830 parts: vec![Part::FunctionResponse {
1831 function_response: FunctionResponseData::new(
1832 name.clone(),
1833 serde_json::json!({ "error": e.to_string() }),
1834 ),
1835 id: id.clone(),
1836 annotations: None,
1837 }],
1838 });
1839 run_after_tool_callbacks = false;
1840 }
1841 }
1842 }
1843
1844 if response_content.is_none() {
1845 let tool_ctx = Arc::new(ToolCallbackContext::new(
1846 self.ctx.clone(),
1847 name.clone(),
1848 final_args.clone(),
1849 ));
1850 for callback in self.before_tool_callbacks.as_ref() {
1851 match callback(tool_ctx.clone() as Arc<dyn CallbackContext>).await {
1852 Ok(Some(c)) => {
1853 response_content = Some(c);
1854 break;
1855 }
1856 Ok(None) => continue,
1857 Err(e) => {
1858 response_content = Some(Content {
1859 role: "function".to_string(),
1860 parts: vec![Part::FunctionResponse {
1861 function_response: FunctionResponseData::new(
1862 name.clone(),
1863 serde_json::json!({ "error": e.to_string() }),
1864 ),
1865 id: id.clone(),
1866 annotations: None,
1867 }],
1868 });
1869 run_after_tool_callbacks = false;
1870 break;
1871 }
1872 }
1873 }
1874 }
1875
1876 if response_content.is_none() {
1878 let guard = self.cb_mutex.lock().unwrap_or_else(|e| e.into_inner());
1879 if let Some(ref cb_state) = *guard
1880 && cb_state.is_open(&name)
1881 {
1882 let msg = format!(
1883 "Tool '{}' is temporarily disabled after {} consecutive failures",
1884 name, cb_state.threshold
1885 );
1886 tracing::warn!(tool.name = %name, "circuit breaker open, skipping tool execution");
1887 response_content = Some(Content {
1888 role: "function".to_string(),
1889 parts: vec![Part::FunctionResponse {
1890 function_response: FunctionResponseData::new(
1891 name.clone(),
1892 serde_json::json!({ "error": msg }),
1893 ),
1894 id: id.clone(),
1895 annotations: None,
1896 }],
1897 });
1898 run_after_tool_callbacks = false;
1899 }
1900 drop(guard);
1901 }
1902
1903 if response_content.is_none() {
1905 if let Some(tool) = self.tool_map.get(&name) {
1906 let tool_ctx: Arc<dyn ToolContext> = Arc::new(
1907 AgentToolContext::new(self.ctx.clone(), function_call_id.clone())
1908 .with_tool_name(tool.name())
1909 .with_progress(self.progress_tx.clone()),
1910 );
1911 let span_name = format!("execute_tool {name}");
1912 let tool_span = tracing::info_span!(
1913 "",
1914 otel.name = %span_name,
1915 tool.name = %name,
1916 "gcp.vertex.agent.event_id" = %format!("{}_{}", self.invocation_id, name),
1917 "gcp.vertex.agent.invocation_id" = %self.invocation_id,
1918 "gcp.vertex.agent.session_id" = %self.ctx.session_id(),
1919 "gen_ai.conversation.id" = %self.ctx.session_id()
1920 );
1921
1922 let budget =
1923 self.tool_retry_budgets.get(&name).or(self.default_retry_budget.as_ref());
1924 let max_attempts = budget.map(|b| b.max_retries + 1).unwrap_or(1);
1925 let retry_delay = budget.map(|b| b.delay).unwrap_or_default();
1926
1927 let tool_clone = tool.clone();
1928 let tool_start = std::time::Instant::now();
1929 let mut last_error = String::new();
1930 let mut final_attempt: u32 = 0;
1931 let mut retry_result: Option<serde_json::Value> = None;
1932
1933 for attempt in 0..max_attempts {
1934 final_attempt = attempt;
1935 if attempt > 0 {
1936 tokio::time::sleep(retry_delay).await;
1937 }
1938 match async {
1939 let args_payload = trace_json_payload(
1940 &final_args,
1941 self.ctx.run_config().record_payloads,
1942 self.ctx.run_config().trace_payload_max_bytes,
1943 );
1944 tracing::debug!(tool.name = %name, tool.args = %args_payload, attempt = attempt, "tool_call");
1945 let exec_future = tool_clone.execute(tool_ctx.clone(), final_args.clone());
1946 let unwind_safe_future = std::panic::AssertUnwindSafe(
1947 tokio::time::timeout(self.tool_timeout, exec_future),
1948 );
1949 match futures::FutureExt::catch_unwind(unwind_safe_future).await {
1950 Ok(result) => result,
1951 Err(_panic) => Ok(Err(adk_core::AdkError::tool(format!(
1952 "tool '{}' panicked during execution",
1953 name
1954 )))),
1955 }
1956 }
1957 .instrument(tool_span.clone())
1958 .await
1959 {
1960 Ok(Ok(value)) => {
1961 let result_payload = trace_json_payload(
1962 &value,
1963 self.ctx.run_config().record_payloads,
1964 self.ctx.run_config().trace_payload_max_bytes,
1965 );
1966 tracing::debug!(tool.name = %name, tool.result = %result_payload, "tool_result");
1967 retry_result = Some(value);
1968 break;
1969 }
1970 Ok(Err(e)) => {
1971 last_error = e.to_string();
1972 if attempt + 1 < max_attempts {
1973 tracing::warn!(tool.name = %name, attempt = attempt, error = %last_error, "tool execution failed, retrying");
1974 } else {
1975 tracing::warn!(tool.name = %name, error = %last_error, "tool_error");
1976 }
1977 }
1978 Err(_) => {
1979 last_error = format!(
1980 "Tool '{}' timed out after {} seconds",
1981 name,
1982 self.tool_timeout.as_secs()
1983 );
1984 if attempt + 1 < max_attempts {
1985 tracing::warn!(tool.name = %name, attempt = attempt, timeout_secs = self.tool_timeout.as_secs(), "tool timed out, retrying");
1986 } else {
1987 tracing::warn!(tool.name = %name, timeout_secs = self.tool_timeout.as_secs(), "tool_timeout");
1988 }
1989 }
1990 }
1991 }
1992
1993 let tool_duration = tool_start.elapsed();
1994 let (tool_success, tool_error_message, function_response) = match retry_result {
1995 Some(value) => (true, None, value),
1996 None => (
1997 false,
1998 Some(last_error.clone()),
1999 serde_json::json!({ "error": last_error }),
2000 ),
2001 };
2002
2003 let outcome = ToolOutcome {
2004 tool_name: name.clone(),
2005 tool_args: final_args.clone(),
2006 success: tool_success,
2007 duration: tool_duration,
2008 error_message: tool_error_message.clone(),
2009 attempt: final_attempt,
2010 };
2011 tool_outcome_for_callback = Some(outcome);
2012
2013 {
2015 let mut guard = self.cb_mutex.lock().unwrap_or_else(|e| e.into_inner());
2016 if let Some(ref mut cb_state) = *guard {
2017 cb_state.record(tool_outcome_for_callback.as_ref().unwrap());
2018 }
2019 }
2020
2021 let final_function_response = if !tool_success {
2023 let mut fallback_result = None;
2024 let error_msg = tool_error_message.clone().unwrap_or_default();
2025 for callback in self.on_tool_error_callbacks.as_ref() {
2026 match callback(
2027 self.ctx.clone() as Arc<dyn CallbackContext>,
2028 tool.clone(),
2029 final_args.clone(),
2030 error_msg.clone(),
2031 )
2032 .await
2033 {
2034 Ok(Some(result)) => {
2035 fallback_result = Some(result);
2036 break;
2037 }
2038 Ok(None) => continue,
2039 Err(e) => {
2040 tracing::warn!(error = %e, "on_tool_error callback failed");
2041 break;
2042 }
2043 }
2044 }
2045 fallback_result.unwrap_or(function_response)
2046 } else {
2047 function_response
2048 };
2049
2050 let confirmation_decision = tool_actions.tool_confirmation_decision;
2051 tool_actions = tool_ctx.actions();
2052 if tool_actions.tool_confirmation_decision.is_none() {
2053 tool_actions.tool_confirmation_decision = confirmation_decision;
2054 }
2055 executed_tool = Some(tool.clone());
2056 executed_tool_response = Some(final_function_response.clone());
2057 response_content = Some(Content {
2058 role: "function".to_string(),
2059 parts: vec![Part::FunctionResponse {
2060 function_response: FunctionResponseData::from_tool_result(
2061 name.clone(),
2062 final_function_response,
2063 ),
2064 id: id.clone(),
2065 annotations: None,
2066 }],
2067 });
2068 } else {
2069 response_content = Some(Content {
2070 role: "function".to_string(),
2071 parts: vec![Part::FunctionResponse {
2072 function_response: FunctionResponseData::new(
2073 name.clone(),
2074 serde_json::json!({
2075 "error": format!("Tool {} not found", name)
2076 }),
2077 ),
2078 id: id.clone(),
2079 annotations: None,
2080 }],
2081 });
2082 }
2083 }
2084
2085 let mut response_content = response_content.expect("tool response content is set");
2087 if run_after_tool_callbacks {
2088 let outcome_ctx: Arc<dyn CallbackContext> = match tool_outcome_for_callback {
2089 Some(outcome) => Arc::new(ToolOutcomeCallbackContext {
2090 inner: self.ctx.clone() as Arc<dyn CallbackContext>,
2091 outcome,
2092 }),
2093 None => self.ctx.clone() as Arc<dyn CallbackContext>,
2094 };
2095 let cb_ctx: Arc<dyn CallbackContext> =
2096 Arc::new(ToolCallbackContext::new(outcome_ctx, name.clone(), final_args.clone()));
2097 for callback in self.after_tool_callbacks.as_ref() {
2098 match callback(cb_ctx.clone()).await {
2099 Ok(Some(modified)) => {
2100 response_content = modified;
2101 break;
2102 }
2103 Ok(None) => continue,
2104 Err(e) => {
2105 response_content = Content {
2106 role: "function".to_string(),
2107 parts: vec![Part::FunctionResponse {
2108 function_response: FunctionResponseData::new(
2109 name.clone(),
2110 serde_json::json!({ "error": e.to_string() }),
2111 ),
2112 id: id.clone(),
2113 annotations: None,
2114 }],
2115 };
2116 break;
2117 }
2118 }
2119 }
2120 if let (Some(tool_ref), Some(tool_resp)) = (&executed_tool, executed_tool_response) {
2121 for callback in self.after_tool_callbacks_full.as_ref() {
2122 match callback(
2123 cb_ctx.clone(),
2124 tool_ref.clone(),
2125 final_args.clone(),
2126 tool_resp.clone(),
2127 )
2128 .await
2129 {
2130 Ok(Some(modified_value)) => {
2131 response_content = Content {
2132 role: "function".to_string(),
2133 parts: vec![Part::FunctionResponse {
2134 function_response: FunctionResponseData::from_tool_result(
2135 name.clone(),
2136 modified_value,
2137 ),
2138 id: id.clone(),
2139 annotations: None,
2140 }],
2141 };
2142 break;
2143 }
2144 Ok(None) => continue,
2145 Err(e) => {
2146 response_content = Content {
2147 role: "function".to_string(),
2148 parts: vec![Part::FunctionResponse {
2149 function_response: FunctionResponseData::new(
2150 name.clone(),
2151 serde_json::json!({ "error": e.to_string() }),
2152 ),
2153 id: id.clone(),
2154 annotations: None,
2155 }],
2156 };
2157 break;
2158 }
2159 }
2160 }
2161 }
2162
2163 #[cfg(feature = "enhanced-plugins")]
2166 if let Some(epm) = self.enhanced_plugin_manager.as_ref()
2167 && let Some(tool_ref) = &executed_tool
2168 {
2169 let result_value = response_content
2171 .parts
2172 .iter()
2173 .find_map(|p| {
2174 if let Part::FunctionResponse { function_response, .. } = p {
2175 Some(function_response.response.clone())
2176 } else {
2177 None
2178 }
2179 })
2180 .unwrap_or(serde_json::json!(null));
2181
2182 match epm
2183 .run_after_tool_call(
2184 tool_ref.clone(),
2185 &final_args,
2186 result_value,
2187 self.ctx.clone() as Arc<dyn CallbackContext>,
2188 )
2189 .await
2190 {
2191 Ok(adk_plugin::AfterToolCallResult::Continue(modified_result)) => {
2192 response_content = Content {
2193 role: "function".to_string(),
2194 parts: vec![Part::FunctionResponse {
2195 function_response: FunctionResponseData::from_tool_result(
2196 name.clone(),
2197 modified_result,
2198 ),
2199 id: id.clone(),
2200 annotations: None,
2201 }],
2202 };
2203 }
2204 Err(e) => {
2205 response_content = Content {
2206 role: "function".to_string(),
2207 parts: vec![Part::FunctionResponse {
2208 function_response: FunctionResponseData::new(
2209 name.clone(),
2210 serde_json::json!({ "error": e.to_string() }),
2211 ),
2212 id: id.clone(),
2213 annotations: None,
2214 }],
2215 };
2216 }
2217 }
2218 }
2219 }
2220
2221 let escalate_or_skip = tool_actions.escalate || tool_actions.skip_summarization;
2222 ToolExecutionResult {
2223 index,
2224 content: response_content,
2225 actions: tool_actions,
2226 escalate_or_skip,
2227 }
2228 }
2229}
2230
2231#[async_trait]
2232impl Agent for LlmAgent {
2233 fn name(&self) -> &str {
2234 &self.name
2235 }
2236
2237 fn description(&self) -> &str {
2238 &self.description
2239 }
2240
2241 fn sub_agents(&self) -> &[Arc<dyn Agent>] {
2242 &self.sub_agents
2243 }
2244
2245 #[adk_telemetry::instrument(
2246 skip(self, ctx),
2247 fields(
2248 agent.name = %self.name,
2249 agent.description = %self.description,
2250 invocation.id = %ctx.invocation_id(),
2251 user.id = %ctx.user_id(),
2252 session.id = %ctx.session_id()
2253 )
2254 )]
2255 async fn run(&self, ctx: Arc<dyn InvocationContext>) -> Result<adk_core::EventStream> {
2256 adk_telemetry::info!("Starting agent execution");
2257 let ctx = Self::apply_input_guardrails(ctx, self.input_guardrails.clone()).await?;
2258
2259 let agent_name = self.name.clone();
2260 let invocation_id = ctx.invocation_id().to_string();
2261 let model = self.model.clone();
2262 let prompt_config = PromptConfig::from_agent(self);
2263 let tool_setup = ToolSetup::from_agent(self);
2264 let output_key = self.output_key.clone();
2265 let output_max_retries = self.output_max_retries;
2266 let generate_content_config = self.generate_content_config.clone();
2267 let max_iterations = self.max_iterations;
2268 let tool_timeout = self.tool_timeout;
2269 let before_agent_callbacks = self.before_callbacks.clone();
2271 let after_agent_callbacks = self.after_callbacks.clone();
2272 let before_model_callbacks = self.before_model_callbacks.clone();
2273 let after_model_callbacks = self.after_model_callbacks.clone();
2274 let before_tool_callbacks = self.before_tool_callbacks.clone();
2275 let after_tool_callbacks = self.after_tool_callbacks.clone();
2276 let on_tool_error_callbacks = self.on_tool_error_callbacks.clone();
2277 let after_tool_callbacks_full = self.after_tool_callbacks_full.clone();
2278 let default_retry_budget = self.default_retry_budget.clone();
2279 let tool_retry_budgets = self.tool_retry_budgets.clone();
2280 let circuit_breaker_threshold = self.circuit_breaker_threshold;
2281 let tool_confirmation_policy = self.tool_confirmation_policy.clone();
2282 let output_guardrails = self.output_guardrails.clone();
2283 let agent_tool_execution_strategy = self.tool_execution_strategy;
2284 #[cfg(feature = "enhanced-plugins")]
2285 let enhanced_plugin_manager = self.enhanced_plugin_manager.clone();
2286
2287 let s = stream! {
2288 let confirmation_decisions =
2289 ctx.run_config().tool_confirmation_decisions.clone();
2290 let confirmation_fingerprints =
2291 ctx.run_config().tool_confirmation_fingerprints.clone();
2292 let mut live_confirmation_decisions =
2293 std::collections::HashMap::<String, ToolConfirmationDecision>::new();
2294 let confirmation_handler = ctx.run_config().tool_confirmation_handler.clone();
2295
2296 for callback in before_agent_callbacks.as_ref() {
2300 match callback(ctx.clone() as Arc<dyn CallbackContext>).await {
2301 Ok(Some(content)) => {
2302 let mut early_event = Event::new(&invocation_id);
2304 early_event.author = agent_name.clone();
2305 early_event.llm_response.content = Some(content);
2306 yield Ok(early_event);
2307
2308 for after_callback in after_agent_callbacks.as_ref() {
2310 match after_callback(ctx.clone() as Arc<dyn CallbackContext>).await {
2311 Ok(Some(after_content)) => {
2312 let mut after_event = Event::new(&invocation_id);
2313 after_event.author = agent_name.clone();
2314 after_event.llm_response.content = Some(after_content);
2315 yield Ok(after_event);
2316 return;
2317 }
2318 Ok(None) => continue,
2319 Err(e) => {
2320 yield Err(e);
2321 return;
2322 }
2323 }
2324 }
2325 return;
2326 }
2327 Ok(None) => {
2328 continue;
2330 }
2331 Err(e) => {
2332 yield Err(e);
2334 return;
2335 }
2336 }
2337 }
2338
2339 let mut conversation_history = match prompt_config
2341 .prepare_conversation(&ctx, &agent_name)
2342 .await
2343 {
2344 Ok(history) => history,
2345 Err(error) => {
2346 yield Err(error);
2347 return;
2348 }
2349 };
2350
2351 let resolved_tools = match tool_setup.resolve(&ctx).await {
2352 Ok(tools) => tools,
2353 Err(error) => {
2354 yield Err(error);
2355 return;
2356 }
2357 };
2358 let tool_map = resolved_tools.map;
2359 let tool_declarations = resolved_tools.declarations;
2360 let valid_transfer_targets = resolved_tools.transfer_targets;
2361
2362 let collect_long_running_ids = |content: &Content| -> Vec<String> {
2363 content
2364 .parts
2365 .iter()
2366 .filter_map(|part| {
2367 if let Part::FunctionCall { name, .. } = part
2368 && let Some(tool) = tool_map.get(name)
2369 && tool.is_long_running()
2370 {
2371 return Some(name.clone());
2372 }
2373 None
2374 })
2375 .collect()
2376 };
2377
2378
2379 let mut circuit_breaker_state = circuit_breaker_threshold.map(CircuitBreakerState::new);
2382
2383 let mut last_interaction_id: Option<String> = None;
2392
2393 let mut iteration = 0;
2395 let mut schema_retry_count: usize = 0;
2396
2397 loop {
2398 if ctx.is_cancelled() {
2401 tracing::info!(agent.name = %agent_name, "invocation cancelled — stopping agent loop");
2402 return;
2403 }
2404 iteration += 1;
2405 if iteration > max_iterations {
2406 yield Err(adk_core::AdkError::agent(
2407 format!("Max iterations ({max_iterations}) exceeded")
2408 ));
2409 return;
2410 }
2411
2412 let config = build_generation_config(
2413 generate_content_config.as_ref(),
2414 prompt_config.output_schema.as_ref(),
2415 ctx.run_config().cached_content.as_deref(),
2416 );
2417
2418 let request = LlmRequest {
2419 model: model.name().to_string(),
2420 contents: conversation_history.clone(),
2421 tools: tool_declarations.clone(),
2422 config,
2423 previous_response_id: last_interaction_id.clone(),
2430 };
2431
2432 #[cfg(feature = "enhanced-plugins")]
2436 let (request, model_response_override_from_plugin) = {
2437 if let Some(epm) = &enhanced_plugin_manager {
2438 match epm.run_before_model_call(request, ctx.clone() as Arc<dyn CallbackContext>).await {
2439 Ok(BeforeModelCallResult::Continue(modified_request)) => {
2440 (modified_request, None)
2441 }
2442 Ok(BeforeModelCallResult::ShortCircuit(response)) => {
2443 (LlmRequest::new("", vec![]), Some(response))
2445 }
2446 Err(e) => {
2447 yield Err(e);
2448 return;
2449 }
2450 }
2451 } else {
2452 (request, None)
2453 }
2454 };
2455 #[cfg(not(feature = "enhanced-plugins"))]
2456 let model_response_override_from_plugin: Option<LlmResponse> = None;
2457
2458 let mut current_request = request;
2461 let mut model_response_override = model_response_override_from_plugin;
2462 if model_response_override.is_none() {
2463 for callback in before_model_callbacks.as_ref() {
2464 match callback(ctx.clone() as Arc<dyn CallbackContext>, current_request.clone()).await {
2465 Ok(BeforeModelResult::Continue(modified_request)) => {
2466 current_request = modified_request;
2468 }
2469 Ok(BeforeModelResult::Skip(response)) => {
2470 model_response_override = Some(response);
2472 break;
2473 }
2474 Err(e) => {
2475 yield Err(e);
2477 return;
2478 }
2479 }
2480 }
2481 }
2482 let request = current_request;
2483
2484 let mut accumulated_content: Option<Content> = None;
2486 let mut final_provider_metadata: Option<serde_json::Value> = None;
2487
2488 if let Some(cached_response) = model_response_override {
2489 accumulated_content = cached_response.content.clone();
2492 final_provider_metadata = cached_response.provider_metadata.clone();
2493 normalize_option_content(&mut accumulated_content);
2494 if let Some(content) = accumulated_content.take() {
2495 let has_function_calls = content
2496 .parts
2497 .iter()
2498 .any(|part| matches!(part, Part::FunctionCall { .. }));
2499 let content = if has_function_calls {
2500 content
2501 } else {
2502 Self::apply_output_guardrails(output_guardrails.as_ref(), content).await?
2503 };
2504 accumulated_content = Some(content);
2505 }
2506
2507 let mut cached_event = Event::new(&invocation_id);
2508 cached_event.author = agent_name.clone();
2509 cached_event.llm_response.content = accumulated_content.clone();
2510 cached_event.llm_response.provider_metadata = cached_response.provider_metadata.clone();
2511 cached_event.llm_response.interaction_id = cached_response.interaction_id.clone();
2513 if cached_response.interaction_id.is_some() {
2514 last_interaction_id = cached_response.interaction_id.clone();
2515 }
2516 cached_event.llm_request = Some(serde_json::to_string(&request).unwrap_or_default());
2517 cached_event.provider_metadata.insert("gcp.vertex.agent.llm_request".to_string(), serde_json::to_string(&request).unwrap_or_default());
2518 cached_event.provider_metadata.insert("gcp.vertex.agent.llm_response".to_string(), serde_json::to_string(&cached_response).unwrap_or_default());
2519
2520 if let Some(ref content) = accumulated_content {
2522 cached_event.long_running_tool_ids = collect_long_running_ids(content);
2523 }
2524
2525 yield Ok(cached_event);
2526 } else {
2527 let request_json = serde_json::to_string(&request).unwrap_or_default();
2529 let trace_request_json = trace_json_payload(
2530 &request,
2531 ctx.run_config().record_payloads,
2532 ctx.run_config().trace_payload_max_bytes,
2533 );
2534
2535 let llm_ts = std::time::SystemTime::now()
2537 .duration_since(std::time::UNIX_EPOCH)
2538 .unwrap_or_default()
2539 .as_nanos();
2540 let llm_event_id = format!("{}_llm_{}", invocation_id, llm_ts);
2541 let llm_span = tracing::info_span!(
2542 "call_llm",
2543 "gcp.vertex.agent.event_id" = %llm_event_id,
2544 "gcp.vertex.agent.invocation_id" = %invocation_id,
2545 "gcp.vertex.agent.session_id" = %ctx.session_id(),
2546 "gen_ai.conversation.id" = %ctx.session_id(),
2547 "gcp.vertex.agent.llm_request" = %trace_request_json,
2548 "gcp.vertex.agent.llm_response" = tracing::field::Empty );
2550 let _llm_guard = llm_span.enter();
2551
2552 use adk_core::StreamingMode;
2554 let streaming_mode = ctx.run_config().streaming_mode;
2555 let should_stream_to_client = matches!(streaming_mode, StreamingMode::SSE | StreamingMode::Bidi)
2556 && output_guardrails.is_empty();
2557
2558 let mut response_stream = model.generate_content(request, true).await?;
2560
2561 use futures::StreamExt;
2562
2563 let mut last_chunk: Option<LlmResponse> = None;
2565
2566 while let Some(chunk_result) = response_stream.next().await {
2568 if ctx.is_cancelled() {
2572 tracing::info!(agent.name = %agent_name, "invocation cancelled during LLM streaming");
2573 return;
2574 }
2575 let mut chunk = match chunk_result {
2576 Ok(c) => c,
2577 Err(e) => {
2578 yield Err(e);
2579 return;
2580 }
2581 };
2582
2583 for callback in after_model_callbacks.as_ref() {
2586 match callback(ctx.clone() as Arc<dyn CallbackContext>, chunk.clone()).await {
2587 Ok(Some(modified_chunk)) => {
2588 chunk = modified_chunk;
2590 break;
2591 }
2592 Ok(None) => {
2593 continue;
2595 }
2596 Err(e) => {
2597 yield Err(e);
2599 return;
2600 }
2601 }
2602 }
2603
2604 normalize_option_content(&mut chunk.content);
2605
2606 if let Some(chunk_content) = chunk.content.clone() {
2608 if let Some(ref mut acc) = accumulated_content {
2609 acc.parts.extend(chunk_content.parts);
2610 } else {
2611 accumulated_content = Some(chunk_content);
2612 }
2613 }
2614
2615 if should_stream_to_client {
2617 let long_running_tool_ids = chunk
2618 .content
2619 .as_ref()
2620 .map(&collect_long_running_ids)
2621 .unwrap_or_default();
2622 yield Ok(build_partial_llm_event(
2623 &llm_event_id,
2624 &invocation_id,
2625 &agent_name,
2626 &request_json,
2627 &chunk,
2628 long_running_tool_ids,
2629 ));
2630 }
2631
2632 if chunk.interaction_id.is_some() {
2636 last_interaction_id = chunk.interaction_id.clone();
2637 }
2638
2639 last_chunk = Some(chunk.clone());
2641
2642 if chunk.turn_complete {
2644 break;
2645 }
2646 }
2647
2648 if !should_stream_to_client {
2650 if let Some(content) = accumulated_content.take() {
2651 let has_function_calls = content
2652 .parts
2653 .iter()
2654 .any(|part| matches!(part, Part::FunctionCall { .. }));
2655 let content = if has_function_calls {
2656 content
2657 } else {
2658 Self::apply_output_guardrails(output_guardrails.as_ref(), content).await?
2659 };
2660 accumulated_content = Some(content);
2661 }
2662
2663 if let Some(last) = &last_chunk {
2664 final_provider_metadata = last.provider_metadata.clone();
2665 }
2666 let long_running_tool_ids = accumulated_content
2667 .as_ref()
2668 .map(&collect_long_running_ids)
2669 .unwrap_or_default();
2670 yield Ok(build_final_llm_event(
2671 &llm_event_id,
2672 &invocation_id,
2673 &agent_name,
2674 &request_json,
2675 accumulated_content.as_ref(),
2676 last_chunk.as_ref(),
2677 long_running_tool_ids,
2678 ));
2679 }
2680
2681 if let Some(ref last) = last_chunk
2692 && let Some(ref code) = last.error_code
2693 {
2694 let message = last
2695 .error_message
2696 .clone()
2697 .unwrap_or_else(|| "provider reported a terminal error".to_string());
2698 tracing::error!(
2699 error.code = %code,
2700 error.message = %message,
2701 agent = %agent_name,
2702 "model reported a terminal error"
2703 );
2704 let mut details = adk_core::ErrorDetails::default();
2711 details
2712 .metadata
2713 .insert("provider_error_code".to_string(), serde_json::json!(code));
2714 let provider_error = adk_core::AdkError::new(
2715 adk_core::ErrorComponent::Model,
2716 adk_core::ErrorCategory::Internal,
2717 "model.provider_error",
2718 format!("{code}: {message}"),
2719 )
2720 .with_details(details);
2721 yield Err(provider_error);
2722 return;
2723 }
2724
2725 if let Some(ref content) = accumulated_content {
2727 let response_json = trace_json_payload(
2728 content,
2729 ctx.run_config().record_payloads,
2730 ctx.run_config().trace_payload_max_bytes,
2731 );
2732 llm_span.record("gcp.vertex.agent.llm_response", &response_json);
2733 }
2734 }
2735
2736 #[cfg(feature = "enhanced-plugins")]
2740 if let Some(epm) = &enhanced_plugin_manager
2741 && let Some(ref content) = accumulated_content {
2742 let response_for_hook = LlmResponse {
2743 content: Some(content.clone()),
2744 provider_metadata: final_provider_metadata.clone(),
2745 ..Default::default()
2746 };
2747 match epm.run_after_model_call(response_for_hook, ctx.clone() as Arc<dyn CallbackContext>).await {
2748 Ok(adk_plugin::AfterModelCallResult::Continue(modified_response)) => {
2749 accumulated_content = modified_response.content;
2750 if modified_response.provider_metadata.is_some() {
2751 final_provider_metadata = modified_response.provider_metadata;
2752 }
2753 }
2754 Err(e) => {
2755 yield Err(e);
2756 return;
2757 }
2758 }
2759 }
2760
2761 let function_call_names: Vec<String> = accumulated_content.as_ref()
2763 .map(|c| c.parts.iter()
2764 .filter_map(|p| {
2765 if let Part::FunctionCall { name, .. } = p {
2766 Some(name.clone())
2767 } else {
2768 None
2769 }
2770 })
2771 .collect())
2772 .unwrap_or_default();
2773
2774 let has_function_calls = !function_call_names.is_empty();
2775
2776 let all_calls_are_long_running = has_function_calls && function_call_names.iter().all(|name| {
2780 tool_map.get(name)
2781 .map(|t| t.is_long_running())
2782 .unwrap_or(false)
2783 });
2784
2785 if let Some(ref content) = accumulated_content {
2787 conversation_history.push(Self::augment_content_for_history(
2788 content,
2789 final_provider_metadata.as_ref(),
2790 ));
2791
2792 if let Some(ref output_key) = output_key
2794 && !has_function_calls
2795 {
2796 let mut text_parts = String::new();
2797 for part in &content.parts {
2798 if let Part::Text { text } = part {
2799 text_parts.push_str(text);
2800 }
2801 }
2802 if !text_parts.is_empty() {
2803 let mut state_event = Event::new(&invocation_id);
2805 state_event.author = agent_name.clone();
2806 state_event.actions.state_delta.insert(
2807 output_key.clone(),
2808 serde_json::Value::String(text_parts),
2809 );
2810 yield Ok(state_event);
2811 }
2812 }
2813 }
2814
2815 if !has_function_calls {
2816 if let Some(schema) = &prompt_config.output_schema {
2821 let text = accumulated_content
2822 .as_ref()
2823 .map(|c| {
2824 c.parts
2825 .iter()
2826 .filter_map(|p| {
2827 if let Part::Text { text } = p {
2828 Some(text.as_str())
2829 } else {
2830 None
2831 }
2832 })
2833 .collect::<Vec<_>>()
2834 .join("")
2835 })
2836 .unwrap_or_default();
2837
2838 if !text.is_empty()
2839 && let Err(validation_error) = validate_output_against_schema(&text, schema)
2840 {
2841 if schema_retry_count >= output_max_retries {
2842 yield Err(adk_core::AdkError::agent(format!(
2843 "output schema validation failed after {} attempts",
2844 output_max_retries
2845 )));
2846 return;
2847 }
2848 schema_retry_count += 1;
2849
2850 let correction = format!(
2852 "Your output did not match the required schema. Error: {}. Please produce valid JSON matching the schema.",
2853 validation_error
2854 );
2855 conversation_history.push(Content {
2856 role: "user".to_string(),
2857 parts: vec![Part::Text { text: correction }],
2858 });
2859 continue;
2860 }
2861 }
2862
2863 if let Some(ref content) = accumulated_content {
2866 let response_json = trace_json_payload(
2867 content,
2868 ctx.run_config().record_payloads,
2869 ctx.run_config().trace_payload_max_bytes,
2870 );
2871 tracing::Span::current().record("gcp.vertex.agent.llm_response", &response_json);
2872 }
2873
2874 tracing::info!(agent.name = %agent_name, "Agent execution complete");
2875 break;
2876 }
2877
2878 if let Some(content) = &accumulated_content {
2880 let strategy = agent_tool_execution_strategy
2883 .unwrap_or(ToolExecutionStrategy::Sequential);
2884
2885 let fc_parts = collect_function_calls(content, &invocation_id);
2886
2887 let mut transfer_handled = false;
2891 for call in &fc_parts {
2892 if call.name == "transfer_to_agent" {
2893 let target_agent = call
2894 .args
2895 .get("agent_name")
2896 .and_then(|value| value.as_str())
2897 .unwrap_or_default()
2898 .to_string();
2899
2900 let valid_target = valid_transfer_targets.iter().any(|n| n == &target_agent);
2901 if !valid_target {
2902 let error_content = Content {
2903 role: "function".to_string(),
2904 parts: vec![Part::FunctionResponse {
2905 function_response: FunctionResponseData::new(
2906 call.name.clone(),
2907 serde_json::json!({
2908 "error": format!(
2909 "Agent '{}' not found. Available agents: {:?}",
2910 target_agent, valid_transfer_targets
2911 )
2912 }),
2913 ),
2914 id: call.id.clone(),
2915 annotations: None,
2916 }],
2917 };
2918 conversation_history.push(error_content.clone());
2919 let mut error_event = Event::new(&invocation_id);
2920 error_event.author = agent_name.clone();
2921 error_event.llm_response.content = Some(error_content);
2922 yield Ok(error_event);
2923 continue;
2924 }
2925
2926 let mut transfer_event = Event::new(&invocation_id);
2927 transfer_event.author = agent_name.clone();
2928 transfer_event.actions.transfer_to_agent = Some(target_agent);
2929 yield Ok(transfer_event);
2930 transfer_handled = true;
2931 break;
2932 }
2933 }
2934 if transfer_handled {
2935 return;
2936 }
2937
2938 let fc_parts: Vec<_> = fc_parts
2940 .into_iter()
2941 .filter(|call| {
2942 if call.name == "transfer_to_agent" {
2943 return false;
2944 }
2945 if let Some(tool) = tool_map.get(&call.name)
2946 && tool.is_builtin()
2947 {
2948 adk_telemetry::debug!(tool.name = %call.name, "skipping built-in tool execution");
2949 return false;
2950 }
2951 true
2952 })
2953 .collect();
2954
2955 let mut confirmation_interrupted = false;
2959 for call in &fc_parts {
2960 if tool_confirmation_policy.requires_confirmation(&call.name)
2961 && static_confirmation_decision(
2962 &confirmation_decisions,
2963 &confirmation_fingerprints,
2964 &call.function_call_id,
2965 &call.name,
2966 &call.args,
2967 )
2968 .is_none()
2969 && live_confirmation_decisions
2970 .get(&call.function_call_id)
2971 .copied()
2972 .is_none()
2973 {
2974 let request = ToolConfirmationRequest {
2975 tool_name: call.name.clone(),
2976 function_call_id: Some(call.function_call_id.clone()),
2977 args: call.args.clone(),
2978 };
2979 if let Some(handler) = confirmation_handler.as_ref() {
2980 match handler.decide(&request).await {
2981 Ok(decision) => {
2982 live_confirmation_decisions
2983 .insert(call.function_call_id.clone(), decision);
2984 continue;
2985 }
2986 Err(error) => {
2987 yield Err(error);
2988 return;
2989 }
2990 }
2991 }
2992
2993 let mut ce = Event::new(&invocation_id);
2994 ce.author = agent_name.clone();
2995 ce.llm_response.interrupted = true;
2996 ce.llm_response.turn_complete = true;
2997 ce.llm_response.content = Some(Content {
2998 role: "model".to_string(),
2999 parts: vec![Part::Text {
3000 text: format!(
3001 "Tool confirmation required for '{}'. Provide approve/deny decision to continue.",
3002 call.name
3003 ),
3004 }],
3005 });
3006 ce.actions.tool_confirmation = Some(request);
3007 yield Ok(ce);
3008 confirmation_interrupted = true;
3009 break;
3010 }
3011 }
3012 if confirmation_interrupted {
3013 return;
3014 }
3015
3016 let cb_mutex = std::sync::Mutex::new(circuit_breaker_state.take());
3018
3019 let concurrency_manager = adk_core::ToolConcurrencyManager::new(
3022 &ctx.run_config().tool_concurrency,
3023 );
3024
3025 let (progress_tx, mut progress_rx) =
3030 tokio::sync::mpsc::channel::<Event>(TOOL_PROGRESS_CAPACITY);
3031
3032 let executor = ToolExecutor {
3033 ctx: ctx.clone(),
3034 tool_map: &tool_map,
3035 tool_retry_budgets: &tool_retry_budgets,
3036 default_retry_budget: &default_retry_budget,
3037 before_tool_callbacks: &before_tool_callbacks,
3038 after_tool_callbacks: &after_tool_callbacks,
3039 after_tool_callbacks_full: &after_tool_callbacks_full,
3040 on_tool_error_callbacks: &on_tool_error_callbacks,
3041 tool_confirmation_policy: &tool_confirmation_policy,
3042 cb_mutex: &cb_mutex,
3043 invocation_id: &invocation_id,
3044 concurrency_manager: &concurrency_manager,
3045 progress_tx: progress_tx.clone(),
3046 tool_timeout,
3047 confirmation_decisions: &confirmation_decisions,
3048 confirmation_fingerprints: &confirmation_fingerprints,
3049 live_confirmation_decisions: &live_confirmation_decisions,
3050 #[cfg(feature = "enhanced-plugins")]
3051 enhanced_plugin_manager: &enhanced_plugin_manager,
3052 };
3053
3054 if ctx.is_cancelled() {
3057 tracing::info!(agent.name = %agent_name, "invocation cancelled before tool dispatch");
3058 return;
3059 }
3060
3061 let mut results = {
3066 let dispatch = async {
3067 let results: Vec<ToolExecutionResult> = match strategy {
3068 ToolExecutionStrategy::Sequential => {
3069 let mut results = Vec::with_capacity(fc_parts.len());
3070 for call in fc_parts {
3071 results.push(executor.execute(call).await);
3072 }
3073 results
3074 }
3075 ToolExecutionStrategy::Parallel => {
3076 use futures::StreamExt as _;
3077 let buffer_size = fc_parts.len().max(1);
3084 futures::stream::iter(
3085 fc_parts.into_iter().map(|call| executor.execute(call)),
3086 )
3087 .buffer_unordered(buffer_size)
3088 .collect()
3089 .await
3090 }
3091 ToolExecutionStrategy::Auto => {
3092 let (concurrent_fcs, sequential_fcs): (Vec<_>, Vec<_>) =
3095 fc_parts.into_iter().partition(|call| {
3096 tool_map.get(&call.name).is_some_and(|tool| {
3097 tool.is_read_only() && tool.is_concurrency_safe()
3098 })
3099 });
3100 let mut all_results = Vec::new();
3101
3102 if !concurrent_fcs.is_empty() {
3105 use futures::StreamExt as _;
3106 let buffer_size = concurrent_fcs.len().max(1);
3107 all_results.extend(
3108 futures::stream::iter(
3109 concurrent_fcs
3110 .into_iter()
3111 .map(|call| executor.execute(call)),
3112 )
3113 .buffer_unordered(buffer_size)
3114 .collect::<Vec<_>>()
3115 .await,
3116 );
3117 }
3118
3119 for call in sequential_fcs {
3121 all_results.push(executor.execute(call).await);
3122 }
3123 all_results
3124 }
3125 };
3126 results
3127 };
3128
3129 tokio::pin!(dispatch);
3135 let results = loop {
3136 tokio::select! {
3137 biased;
3138 Some(progress_event) = progress_rx.recv() => {
3139 yield Ok(progress_event);
3140 }
3141 done = &mut dispatch => break done,
3142 }
3143 };
3144 while let Ok(progress_event) = progress_rx.try_recv() {
3146 yield Ok(progress_event);
3147 }
3148 results
3149 };
3150 results.sort_by_key(|r| r.index);
3152
3153 circuit_breaker_state = cb_mutex.into_inner().unwrap_or_else(|e| e.into_inner());
3155
3156 for result in results {
3158 let mut tool_event = Event::new(&invocation_id);
3159 tool_event.author = agent_name.clone();
3160 tool_event.actions = result.actions;
3161 tool_event.llm_response.content = Some(result.content.clone());
3162 yield Ok(tool_event);
3163
3164 if result.escalate_or_skip {
3165 return;
3166 }
3167
3168 conversation_history.push(result.content);
3169 }
3170 }
3171
3172 if all_calls_are_long_running {
3176 }
3180 }
3181
3182 for callback in after_agent_callbacks.as_ref() {
3185 match callback(ctx.clone() as Arc<dyn CallbackContext>).await {
3186 Ok(Some(content)) => {
3187 let mut after_event = Event::new(&invocation_id);
3189 after_event.author = agent_name.clone();
3190 after_event.llm_response.content = Some(content);
3191 yield Ok(after_event);
3192 break; }
3194 Ok(None) => {
3195 continue;
3197 }
3198 Err(e) => {
3199 yield Err(e);
3201 return;
3202 }
3203 }
3204 }
3205 };
3206
3207 Ok(Box::pin(s))
3208 }
3209}
3210
3211#[cfg(test)]
3212mod run_helper_tests {
3213 use super::*;
3214
3215 #[test]
3216 fn generation_config_layers_schema_and_cached_content() {
3217 let base =
3218 adk_core::GenerateContentConfig { temperature: Some(0.25), ..Default::default() };
3219 let schema = serde_json::json!({"type": "object"});
3220
3221 let config = build_generation_config(Some(&base), Some(&schema), Some("cached/example"))
3222 .expect("config should be present");
3223
3224 assert_eq!(config.temperature, Some(0.25));
3225 assert_eq!(config.response_schema, Some(schema));
3226 assert_eq!(config.cached_content.as_deref(), Some("cached/example"));
3227 }
3228
3229 #[test]
3230 fn function_calls_preserve_order_and_create_fallback_ids() {
3231 let content = Content {
3232 role: "model".to_string(),
3233 parts: vec![
3234 Part::Text { text: "before".to_string() },
3235 Part::FunctionCall {
3236 name: "first".to_string(),
3237 args: serde_json::json!({"value": 1}),
3238 id: None,
3239 thought_signature: None,
3240 },
3241 Part::FunctionCall {
3242 name: "second".to_string(),
3243 args: serde_json::json!({"value": 2}),
3244 id: Some("provider-id".to_string()),
3245 thought_signature: None,
3246 },
3247 ],
3248 };
3249
3250 let calls = collect_function_calls(&content, "invocation");
3251
3252 assert_eq!(calls.len(), 2);
3253 assert_eq!(calls[0].index, 0);
3254 assert_eq!(calls[0].name, "first");
3255 assert_eq!(calls[0].function_call_id, "invocation_first_0");
3256 assert_eq!(calls[1].index, 1);
3257 assert_eq!(calls[1].name, "second");
3258 assert_eq!(calls[1].function_call_id, "provider-id");
3259 }
3260}