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 event.llm_request = Some(request_json.to_string());
148 event
149 .provider_metadata
150 .insert("gcp.vertex.agent.llm_request".to_string(), request_json.to_string());
151 event.provider_metadata.insert(
152 "gcp.vertex.agent.llm_response".to_string(),
153 serde_json::to_string(chunk).unwrap_or_default(),
154 );
155 event.llm_response.partial = chunk.partial;
156 event.llm_response.turn_complete = chunk.turn_complete;
157 event.llm_response.finish_reason = chunk.finish_reason;
158 event.llm_response.usage_metadata = chunk.usage_metadata.clone();
159 event.llm_response.content = chunk.content.clone();
160 event.llm_response.provider_metadata = chunk.provider_metadata.clone();
161 event.llm_response.interaction_id = chunk.interaction_id.clone();
162 event.llm_response.interrupted = chunk.interrupted;
165 event.llm_response.error_code = chunk.error_code.clone();
166 event.llm_response.error_message = chunk.error_message.clone();
167 event.long_running_tool_ids = long_running_tool_ids;
168 event
169}
170
171fn build_final_llm_event(
172 event_id: &str,
173 invocation_id: &str,
174 agent_name: &str,
175 request_json: &str,
176 content: Option<&Content>,
177 last_chunk: Option<&LlmResponse>,
178 long_running_tool_ids: Vec<String>,
179) -> Event {
180 let mut event = Event::with_id(event_id, invocation_id);
181 event.author = agent_name.to_string();
182 event.llm_request = Some(request_json.to_string());
183 event
184 .provider_metadata
185 .insert("gcp.vertex.agent.llm_request".to_string(), request_json.to_string());
186 event.llm_response.content = content.cloned();
187 event.llm_response.partial = false;
188 event.llm_response.turn_complete = true;
189
190 if let Some(last_chunk) = last_chunk {
191 event.llm_response.finish_reason = last_chunk.finish_reason;
192 event.llm_response.usage_metadata = last_chunk.usage_metadata.clone();
193 event.llm_response.provider_metadata = last_chunk.provider_metadata.clone();
194 event.llm_response.interaction_id = last_chunk.interaction_id.clone();
195 event.llm_response.interrupted = last_chunk.interrupted;
196 event.llm_response.error_code = last_chunk.error_code.clone();
197 event.llm_response.error_message = last_chunk.error_message.clone();
198 event.provider_metadata.insert(
199 "gcp.vertex.agent.llm_response".to_string(),
200 serde_json::to_string(last_chunk).unwrap_or_default(),
201 );
202 }
203
204 event.long_running_tool_ids = long_running_tool_ids;
205 event
206}
207
208pub struct LlmAgent {
216 name: String,
217 description: String,
218 model: Arc<dyn Llm>,
219 instruction: Option<String>,
220 instruction_provider: Option<Arc<InstructionProvider>>,
221 global_instruction: Option<String>,
222 global_instruction_provider: Option<Arc<GlobalInstructionProvider>>,
223 skills_index: Option<Arc<SkillIndex>>,
224 skill_policy: SelectionPolicy,
225 max_skill_chars: usize,
226 #[allow(dead_code)] input_schema: Option<serde_json::Value>,
228 output_schema: Option<serde_json::Value>,
229 output_max_retries: usize,
231 disallow_transfer_to_parent: bool,
232 disallow_transfer_to_peers: bool,
233 include_contents: adk_core::IncludeContents,
234 tools: Vec<Arc<dyn Tool>>,
235 toolsets: Vec<Arc<dyn Toolset>>,
236 sub_agents: Vec<Arc<dyn Agent>>,
237 output_key: Option<String>,
238 generate_content_config: Option<adk_core::GenerateContentConfig>,
240 max_iterations: u32,
242 tool_timeout: std::time::Duration,
244 before_callbacks: Arc<Vec<BeforeAgentCallback>>,
245 after_callbacks: Arc<Vec<AfterAgentCallback>>,
246 before_model_callbacks: Arc<Vec<BeforeModelCallback>>,
247 after_model_callbacks: Arc<Vec<AfterModelCallback>>,
248 before_tool_callbacks: Arc<Vec<BeforeToolCallback>>,
249 after_tool_callbacks: Arc<Vec<AfterToolCallback>>,
250 on_tool_error_callbacks: Arc<Vec<OnToolErrorCallback>>,
251 after_tool_callbacks_full: Arc<Vec<AfterToolCallbackFull>>,
253 default_retry_budget: Option<RetryBudget>,
255 tool_retry_budgets: std::collections::HashMap<String, RetryBudget>,
257 circuit_breaker_threshold: Option<u32>,
260 tool_confirmation_policy: ToolConfirmationPolicy,
261 tool_execution_strategy: Option<ToolExecutionStrategy>,
264 input_guardrails: Arc<GuardrailSet>,
265 output_guardrails: Arc<GuardrailSet>,
266 tool_guardrails: Arc<ToolGuardrailSet>,
267 #[cfg(feature = "enhanced-plugins")]
270 enhanced_plugin_manager: Option<Arc<EnhancedPluginManager>>,
271 #[cfg(feature = "sandbox")]
275 sandbox_config: Option<adk_sandbox::workspace::SandboxConfig>,
276}
277
278struct PromptConfig {
279 instruction: Option<String>,
280 instruction_provider: Option<Arc<InstructionProvider>>,
281 global_instruction: Option<String>,
282 global_instruction_provider: Option<Arc<GlobalInstructionProvider>>,
283 skills_index: Option<Arc<SkillIndex>>,
284 skill_policy: SelectionPolicy,
285 max_skill_chars: usize,
286 output_schema: Option<serde_json::Value>,
287 include_contents: adk_core::IncludeContents,
288}
289
290impl PromptConfig {
291 fn from_agent(agent: &LlmAgent) -> Self {
292 Self {
293 instruction: agent.instruction.clone(),
294 instruction_provider: agent.instruction_provider.clone(),
295 global_instruction: agent.global_instruction.clone(),
296 global_instruction_provider: agent.global_instruction_provider.clone(),
297 skills_index: agent.skills_index.clone(),
298 skill_policy: agent.skill_policy.clone(),
299 max_skill_chars: agent.max_skill_chars,
300 output_schema: agent.output_schema.clone(),
301 include_contents: agent.include_contents,
302 }
303 }
304
305 async fn prepare_conversation(
306 &self,
307 ctx: &Arc<dyn InvocationContext>,
308 agent_name: &str,
309 ) -> Result<Vec<Content>> {
310 let mut preamble = Vec::new();
311
312 if let Some(provider) = &self.global_instruction_provider {
313 let instruction = provider(ctx.clone() as Arc<dyn ReadonlyContext>).await?;
314 if !instruction.is_empty() {
315 preamble.push(Content::new("user").with_text(instruction));
316 }
317 } else if let Some(template) = &self.global_instruction {
318 let instruction = adk_core::inject_session_state(ctx.as_ref(), template).await?;
319 if !instruction.is_empty() {
320 preamble.push(Content::new("user").with_text(instruction));
321 }
322 }
323
324 if let Some(provider) = &self.instruction_provider {
325 let instruction = provider(ctx.clone() as Arc<dyn ReadonlyContext>).await?;
326 if !instruction.is_empty() {
327 preamble.push(Content::new("user").with_text(instruction));
328 }
329 } else if let Some(template) = &self.instruction {
330 let instruction = adk_core::inject_session_state(ctx.as_ref(), template).await?;
331 if !instruction.is_empty() {
332 preamble.push(Content::new("user").with_text(instruction));
333 }
334 }
335
336 if let Some(schema) = &self.output_schema {
337 preamble.push(Content::new("user").with_text(format!(
338 "You MUST respond with valid JSON conforming to this schema: {schema}. Do not include any text outside the JSON object."
339 )));
340 }
341
342 let agent_filter = if ctx.authoritative_transfer_targets()
343 || !ctx.run_config().transfer_targets.is_empty()
344 {
345 Some(agent_name)
346 } else {
347 None
348 };
349 let mut session_history =
350 ctx.session().conversation_history_scoped(agent_filter, ctx.branch());
351 let mut current_user_content = ctx.user_content().clone();
352 if let Some(index) = &self.skills_index {
353 apply_skill_injection(
354 &mut current_user_content,
355 index.as_ref(),
356 &self.skill_policy,
357 self.max_skill_chars,
358 );
359 }
360 if let Some(index) = session_history.iter().rposition(|content| content.role == "user") {
361 session_history[index] = current_user_content.clone();
362 } else {
363 session_history.push(current_user_content.clone());
364 }
365
366 Ok(match self.include_contents {
367 adk_core::IncludeContents::None => {
368 preamble.push(current_user_content);
369 preamble
370 }
371 adk_core::IncludeContents::Default => {
372 preamble.extend(session_history);
373 preamble
374 }
375 })
376 }
377}
378
379struct ToolSetup {
380 tools: Vec<Arc<dyn Tool>>,
381 toolsets: Vec<Arc<dyn Toolset>>,
382 sub_agents: Vec<Arc<dyn Agent>>,
383 disallow_transfer_to_parent: bool,
384 disallow_transfer_to_peers: bool,
385}
386
387struct ResolvedTools {
388 map: HashMap<String, Arc<dyn Tool>>,
389 declarations: HashMap<String, serde_json::Value>,
390 transfer_targets: Vec<String>,
391}
392
393impl ToolSetup {
394 fn from_agent(agent: &LlmAgent) -> Self {
395 Self {
396 tools: agent.tools.clone(),
397 toolsets: agent.toolsets.clone(),
398 sub_agents: agent.sub_agents.clone(),
399 disallow_transfer_to_parent: agent.disallow_transfer_to_parent,
400 disallow_transfer_to_peers: agent.disallow_transfer_to_peers,
401 }
402 }
403
404 async fn resolve(&self, ctx: &Arc<dyn InvocationContext>) -> Result<ResolvedTools> {
405 let mut tools = self.tools.clone();
406 let static_tool_names: std::collections::HashSet<_> =
407 tools.iter().map(|tool| tool.name().to_string()).collect();
408 let mut toolset_sources = std::collections::HashMap::<String, String>::new();
409 let mut active_toolsets: Vec<&dyn Toolset> =
410 self.toolsets.iter().map(AsRef::as_ref).collect();
411 active_toolsets.extend(
412 ctx.run_config().runtime_toolsets.iter().map(|runtime| runtime.toolset().as_ref()),
413 );
414
415 for toolset in active_toolsets {
416 for tool in toolset.tools(ctx.clone() as Arc<dyn ReadonlyContext>).await? {
417 let name = tool.name().to_string();
418 if static_tool_names.contains(&name) {
419 return Err(adk_core::AdkError::agent(format!(
420 "Duplicate tool name '{name}': conflict between static tool and toolset '{}'",
421 toolset.name()
422 )));
423 }
424 if let Some(other_toolset) = toolset_sources.get(&name) {
425 return Err(adk_core::AdkError::agent(format!(
426 "Duplicate tool name '{name}': conflict between toolset '{other_toolset}' and toolset '{}'",
427 toolset.name()
428 )));
429 }
430 toolset_sources.insert(name, toolset.name().to_string());
431 tools.push(tool);
432 }
433 }
434
435 let map = tools.iter().map(|tool| (tool.name().to_string(), tool.clone())).collect();
436 let mut declarations = tools
437 .iter()
438 .map(|tool| (tool.name().to_string(), tool.declaration()))
439 .collect::<std::collections::HashMap<_, _>>();
440 let mut transfer_targets: Vec<String> = if ctx.authoritative_transfer_targets() {
441 Vec::new()
442 } else {
443 self.sub_agents.iter().map(|agent| agent.name().to_string()).collect()
444 };
445 let child_names: std::collections::HashSet<_> =
446 self.sub_agents.iter().map(|agent| agent.name()).collect();
447 let parent_name = ctx.run_config().parent_agent.as_deref();
448
449 for target in &ctx.run_config().transfer_targets {
450 if child_names.contains(target.as_str()) {
451 continue;
452 }
453 let is_parent = parent_name == Some(target.as_str());
454 if (is_parent && self.disallow_transfer_to_parent)
455 || (!is_parent && self.disallow_transfer_to_peers)
456 {
457 continue;
458 }
459 transfer_targets.push(target.clone());
460 }
461
462 if !transfer_targets.is_empty() {
463 declarations.insert(
464 "transfer_to_agent".to_string(),
465 serde_json::json!({
466 "name": "transfer_to_agent",
467 "description": format!(
468 "Transfer execution to another agent. Valid targets: {}",
469 transfer_targets.join(", ")
470 ),
471 "parameters": {
472 "type": "object",
473 "properties": {
474 "agent_name": {
475 "type": "string",
476 "description": "The name of the agent to transfer to.",
477 "enum": transfer_targets
478 }
479 },
480 "required": ["agent_name"]
481 }
482 }),
483 );
484 }
485
486 Ok(ResolvedTools { map, declarations, transfer_targets })
487 }
488}
489
490impl std::fmt::Debug for LlmAgent {
491 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
492 f.debug_struct("LlmAgent")
493 .field("name", &self.name)
494 .field("description", &self.description)
495 .field("model", &self.model.name())
496 .field("instruction", &self.instruction)
497 .field("tools_count", &self.tools.len())
498 .field("sub_agents_count", &self.sub_agents.len())
499 .finish()
500 }
501}
502
503fn static_confirmation_decision(
511 decisions: &std::collections::HashMap<String, ToolConfirmationDecision>,
512 fingerprints: &std::collections::HashMap<String, String>,
513 function_call_id: &str,
514 tool_name: &str,
515 args: &serde_json::Value,
516) -> Option<ToolConfirmationDecision> {
517 let decision = decisions.get(function_call_id).copied()?;
518 if let Some(expected) = fingerprints.get(function_call_id) {
519 let actual = adk_core::tool_call_fingerprint(tool_name, args);
520 if &actual != expected {
521 tracing::warn!(
522 tool.name = %tool_name,
523 function_call.id = %function_call_id,
524 "confirmation decision does not match this call's arguments, treating as unconfirmed"
525 );
526 return None;
527 }
528 }
529 Some(decision)
530}
531
532impl LlmAgent {
533 #[cfg(feature = "sandbox")]
540 pub fn sandbox_config(&self) -> Option<&adk_sandbox::workspace::SandboxConfig> {
541 self.sandbox_config.as_ref()
542 }
543
544 async fn apply_input_guardrails(
545 ctx: Arc<dyn InvocationContext>,
546 input_guardrails: Arc<GuardrailSet>,
547 ) -> Result<Arc<dyn InvocationContext>> {
548 let content =
549 enforce_guardrails(input_guardrails.as_ref(), ctx.user_content(), "input").await?;
550 if content.role != ctx.user_content().role || content.parts != ctx.user_content().parts {
551 Ok(with_user_content_override(ctx, content))
552 } else {
553 Ok(ctx)
554 }
555 }
556
557 async fn apply_output_guardrails(
558 output_guardrails: &GuardrailSet,
559 content: Content,
560 ) -> Result<Content> {
561 enforce_guardrails(output_guardrails, &content, "output").await
562 }
563
564 fn history_parts_from_provider_metadata(
565 provider_metadata: Option<&serde_json::Value>,
566 ) -> Vec<Part> {
567 let Some(provider_metadata) = provider_metadata else {
568 return Vec::new();
569 };
570
571 let history_parts = provider_metadata
572 .get("conversation_history_parts")
573 .or_else(|| {
574 provider_metadata
575 .get("openai")
576 .and_then(|openai| openai.get("conversation_history_parts"))
577 })
578 .and_then(serde_json::Value::as_array);
579
580 history_parts
581 .into_iter()
582 .flatten()
583 .filter_map(|value| serde_json::from_value::<Part>(value.clone()).ok())
584 .collect()
585 }
586
587 fn augment_content_for_history(
588 content: &Content,
589 provider_metadata: Option<&serde_json::Value>,
590 ) -> Content {
591 let mut augmented = content.clone();
592 augmented.parts.extend(Self::history_parts_from_provider_metadata(provider_metadata));
593 augmented
594 }
595}
596
597fn validate_output_against_schema(
602 text: &str,
603 schema: &serde_json::Value,
604) -> std::result::Result<serde_json::Value, String> {
605 let parsed: serde_json::Value =
606 serde_json::from_str(text).map_err(|e| format!("Response is not valid JSON: {e}"))?;
607
608 let validator =
609 jsonschema::validator_for(schema).map_err(|e| format!("Invalid schema: {e}"))?;
610
611 let errors: Vec<String> = validator.iter_errors(&parsed).map(|e| e.to_string()).collect();
612
613 if errors.is_empty() { Ok(parsed) } else { Err(errors.join("; ")) }
614}
615
616fn extract_text_from_events(events: &[Event]) -> Option<String> {
621 for event in events.iter().rev() {
622 if let Some(ref content) = event.llm_response.content {
623 let text: String =
624 content
625 .parts
626 .iter()
627 .filter_map(|p| {
628 if let Part::Text { text } = p { Some(text.as_str()) } else { None }
629 })
630 .collect::<Vec<_>>()
631 .join("");
632 if !text.is_empty() {
633 return Some(text);
634 }
635 }
636 }
637 None
638}
639
640pub fn extract_typed<T: serde::de::DeserializeOwned>(events: &[Event]) -> Result<T> {
662 let text = extract_text_from_events(events).ok_or_else(|| {
663 adk_core::AdkError::agent("no text content found in events for typed extraction")
664 })?;
665
666 serde_json::from_str(&text)
667 .map_err(|e| adk_core::AdkError::agent(format!("output deserialization failed: {e}")))
668}
669
670pub struct LlmAgentBuilder {
672 name: String,
673 description: Option<String>,
674 model: Option<Arc<dyn Llm>>,
675 instruction: Option<String>,
676 instruction_provider: Option<Arc<InstructionProvider>>,
677 global_instruction: Option<String>,
678 global_instruction_provider: Option<Arc<GlobalInstructionProvider>>,
679 skills_index: Option<Arc<SkillIndex>>,
680 skill_policy: SelectionPolicy,
681 max_skill_chars: usize,
682 input_schema: Option<serde_json::Value>,
683 output_schema: Option<serde_json::Value>,
684 output_max_retries: usize,
685 disallow_transfer_to_parent: bool,
686 disallow_transfer_to_peers: bool,
687 include_contents: adk_core::IncludeContents,
688 tools: Vec<Arc<dyn Tool>>,
689 toolsets: Vec<Arc<dyn Toolset>>,
690 sub_agents: Vec<Arc<dyn Agent>>,
691 output_key: Option<String>,
692 generate_content_config: Option<adk_core::GenerateContentConfig>,
693 max_iterations: u32,
694 tool_timeout: std::time::Duration,
695 before_callbacks: Vec<BeforeAgentCallback>,
696 after_callbacks: Vec<AfterAgentCallback>,
697 before_model_callbacks: Vec<BeforeModelCallback>,
698 after_model_callbacks: Vec<AfterModelCallback>,
699 before_tool_callbacks: Vec<BeforeToolCallback>,
700 after_tool_callbacks: Vec<AfterToolCallback>,
701 on_tool_error_callbacks: Vec<OnToolErrorCallback>,
702 after_tool_callbacks_full: Vec<AfterToolCallbackFull>,
703 default_retry_budget: Option<RetryBudget>,
704 tool_retry_budgets: std::collections::HashMap<String, RetryBudget>,
705 circuit_breaker_threshold: Option<u32>,
706 tool_confirmation_policy: ToolConfirmationPolicy,
707 tool_execution_strategy: Option<ToolExecutionStrategy>,
708 input_guardrails: GuardrailSet,
709 output_guardrails: GuardrailSet,
710 tool_guardrails: ToolGuardrailSet,
711 #[cfg(feature = "enhanced-plugins")]
713 enhanced_plugins: Vec<Arc<dyn EnhancedPlugin>>,
714 #[cfg(feature = "sandbox")]
716 sandbox_config: Option<adk_sandbox::workspace::SandboxConfig>,
717}
718
719impl LlmAgentBuilder {
720 pub fn new(name: impl Into<String>) -> Self {
722 Self {
723 name: name.into(),
724 description: None,
725 model: None,
726 instruction: None,
727 instruction_provider: None,
728 global_instruction: None,
729 global_instruction_provider: None,
730 skills_index: None,
731 skill_policy: SelectionPolicy::default(),
732 max_skill_chars: 2000,
733 input_schema: None,
734 output_schema: None,
735 output_max_retries: 3,
736 disallow_transfer_to_parent: false,
737 disallow_transfer_to_peers: false,
738 include_contents: adk_core::IncludeContents::Default,
739 tools: Vec::new(),
740 toolsets: Vec::new(),
741 sub_agents: Vec::new(),
742 output_key: None,
743 generate_content_config: None,
744 max_iterations: DEFAULT_MAX_ITERATIONS,
745 tool_timeout: DEFAULT_TOOL_TIMEOUT,
746 before_callbacks: Vec::new(),
747 after_callbacks: Vec::new(),
748 before_model_callbacks: Vec::new(),
749 after_model_callbacks: Vec::new(),
750 before_tool_callbacks: Vec::new(),
751 after_tool_callbacks: Vec::new(),
752 on_tool_error_callbacks: Vec::new(),
753 after_tool_callbacks_full: Vec::new(),
754 default_retry_budget: None,
755 tool_retry_budgets: std::collections::HashMap::new(),
756 circuit_breaker_threshold: None,
757 tool_confirmation_policy: ToolConfirmationPolicy::Never,
758 tool_execution_strategy: None,
759 input_guardrails: GuardrailSet::new(),
760 output_guardrails: GuardrailSet::new(),
761 tool_guardrails: ToolGuardrailSet::new(),
762 #[cfg(feature = "enhanced-plugins")]
763 enhanced_plugins: Vec::new(),
764 #[cfg(feature = "sandbox")]
765 sandbox_config: None,
766 }
767 }
768
769 pub fn description(mut self, desc: impl Into<String>) -> Self {
771 self.description = Some(desc.into());
772 self
773 }
774
775 pub fn model(mut self, model: Arc<dyn Llm>) -> Self {
777 self.model = Some(model);
778 self
779 }
780
781 pub fn instruction(mut self, instruction: impl Into<String>) -> Self {
783 self.instruction = Some(instruction.into());
784 self
785 }
786
787 pub fn instruction_provider(mut self, provider: InstructionProvider) -> Self {
789 self.instruction_provider = Some(Arc::new(provider));
790 self
791 }
792
793 pub fn global_instruction(mut self, instruction: impl Into<String>) -> Self {
795 self.global_instruction = Some(instruction.into());
796 self
797 }
798
799 pub fn global_instruction_provider(mut self, provider: GlobalInstructionProvider) -> Self {
801 self.global_instruction_provider = Some(Arc::new(provider));
802 self
803 }
804
805 #[cfg(feature = "skills")]
810 pub fn with_skills(mut self, index: SkillIndex) -> Self {
811 self.skills_index = Some(Arc::new(index));
812 self
813 }
814
815 #[cfg(feature = "skills")]
817 pub fn with_auto_skills(self) -> Result<Self> {
818 self.with_skills_from_root(".")
819 }
820
821 #[cfg(feature = "skills")]
823 pub fn with_skills_from_root(mut self, root: impl AsRef<std::path::Path>) -> Result<Self> {
824 let index = load_skill_index(root).map_err(|e| adk_core::AdkError::agent(e.to_string()))?;
825 self.skills_index = Some(Arc::new(index));
826 Ok(self)
827 }
828
829 #[cfg(feature = "skills")]
831 pub fn with_skill_policy(mut self, policy: SelectionPolicy) -> Self {
832 self.skill_policy = policy;
833 self
834 }
835
836 #[cfg(feature = "skills")]
838 pub fn with_skill_budget(mut self, max_chars: usize) -> Self {
839 self.max_skill_chars = max_chars;
840 self
841 }
842
843 pub fn input_schema(mut self, schema: serde_json::Value) -> Self {
845 self.input_schema = Some(schema);
846 self
847 }
848
849 pub fn output_schema(mut self, schema: serde_json::Value) -> Self {
851 self.output_schema = Some(schema);
852 self
853 }
854
855 pub fn output_type<T: schemars::JsonSchema>(mut self) -> Self {
878 let schema = schemars::schema_for!(T);
879 self.output_schema =
880 Some(serde_json::to_value(schema).expect("schema serialization cannot fail"));
881 self
882 }
883
884 pub fn output_max_retries(mut self, n: usize) -> Self {
889 self.output_max_retries = n;
890 self
891 }
892
893 pub fn disallow_transfer_to_parent(mut self, disallow: bool) -> Self {
895 self.disallow_transfer_to_parent = disallow;
896 self
897 }
898
899 pub fn disallow_transfer_to_peers(mut self, disallow: bool) -> Self {
901 self.disallow_transfer_to_peers = disallow;
902 self
903 }
904
905 pub fn include_contents(mut self, include: adk_core::IncludeContents) -> Self {
907 self.include_contents = include;
908 self
909 }
910
911 pub fn output_key(mut self, key: impl Into<String>) -> Self {
913 self.output_key = Some(key.into());
914 self
915 }
916
917 pub fn generate_content_config(mut self, config: adk_core::GenerateContentConfig) -> Self {
938 self.generate_content_config = Some(config);
939 self
940 }
941
942 pub fn temperature(mut self, temperature: f32) -> Self {
945 self.generate_content_config
946 .get_or_insert(adk_core::GenerateContentConfig::default())
947 .temperature = Some(temperature);
948 self
949 }
950
951 pub fn top_p(mut self, top_p: f32) -> Self {
953 self.generate_content_config
954 .get_or_insert(adk_core::GenerateContentConfig::default())
955 .top_p = Some(top_p);
956 self
957 }
958
959 pub fn top_k(mut self, top_k: i32) -> Self {
961 self.generate_content_config
962 .get_or_insert(adk_core::GenerateContentConfig::default())
963 .top_k = Some(top_k);
964 self
965 }
966
967 pub fn max_output_tokens(mut self, max_tokens: i32) -> Self {
969 self.generate_content_config
970 .get_or_insert(adk_core::GenerateContentConfig::default())
971 .max_output_tokens = Some(max_tokens);
972 self
973 }
974
975 pub fn max_iterations(mut self, max: u32) -> Self {
978 self.max_iterations = max;
979 self
980 }
981
982 pub fn tool_timeout(mut self, timeout: std::time::Duration) -> Self {
985 self.tool_timeout = timeout;
986 self
987 }
988
989 pub fn tool(mut self, tool: Arc<dyn Tool>) -> Self {
991 self.tools.push(tool);
992 self
993 }
994
995 pub fn toolset(mut self, toolset: Arc<dyn Toolset>) -> Self {
1001 self.toolsets.push(toolset);
1002 self
1003 }
1004
1005 pub fn sub_agent(mut self, agent: Arc<dyn Agent>) -> Self {
1007 self.sub_agents.push(agent);
1008 self
1009 }
1010
1011 pub fn before_callback(mut self, callback: BeforeAgentCallback) -> Self {
1013 self.before_callbacks.push(callback);
1014 self
1015 }
1016
1017 pub fn after_callback(mut self, callback: AfterAgentCallback) -> Self {
1019 self.after_callbacks.push(callback);
1020 self
1021 }
1022
1023 pub fn before_model_callback(mut self, callback: BeforeModelCallback) -> Self {
1025 self.before_model_callbacks.push(callback);
1026 self
1027 }
1028
1029 pub fn after_model_callback(mut self, callback: AfterModelCallback) -> Self {
1031 self.after_model_callbacks.push(callback);
1032 self
1033 }
1034
1035 pub fn before_tool_callback(mut self, callback: BeforeToolCallback) -> Self {
1037 self.before_tool_callbacks.push(callback);
1038 self
1039 }
1040
1041 pub fn after_tool_callback(mut self, callback: AfterToolCallback) -> Self {
1043 self.after_tool_callbacks.push(callback);
1044 self
1045 }
1046
1047 pub fn after_tool_callback_full(mut self, callback: AfterToolCallbackFull) -> Self {
1062 self.after_tool_callbacks_full.push(callback);
1063 self
1064 }
1065
1066 pub fn on_tool_error(mut self, callback: OnToolErrorCallback) -> Self {
1074 self.on_tool_error_callbacks.push(callback);
1075 self
1076 }
1077
1078 pub fn default_retry_budget(mut self, budget: RetryBudget) -> Self {
1085 self.default_retry_budget = Some(budget);
1086 self
1087 }
1088
1089 pub fn tool_retry_budget(mut self, tool_name: impl Into<String>, budget: RetryBudget) -> Self {
1094 self.tool_retry_budgets.insert(tool_name.into(), budget);
1095 self
1096 }
1097
1098 pub fn circuit_breaker_threshold(mut self, threshold: u32) -> Self {
1105 self.circuit_breaker_threshold = Some(threshold);
1106 self
1107 }
1108
1109 pub fn tool_confirmation_policy(mut self, policy: ToolConfirmationPolicy) -> Self {
1111 self.tool_confirmation_policy = policy;
1112 self
1113 }
1114
1115 pub fn require_tool_confirmation(mut self, tool_name: impl Into<String>) -> Self {
1117 self.tool_confirmation_policy = self.tool_confirmation_policy.with_tool(tool_name);
1118 self
1119 }
1120
1121 pub fn require_tool_confirmation_for_all(mut self) -> Self {
1123 self.tool_confirmation_policy = ToolConfirmationPolicy::Always;
1124 self
1125 }
1126
1127 pub fn tool_execution_strategy(mut self, strategy: ToolExecutionStrategy) -> Self {
1135 self.tool_execution_strategy = Some(strategy);
1136 self
1137 }
1138
1139 pub fn input_guardrails(mut self, guardrails: GuardrailSet) -> Self {
1148 self.input_guardrails = guardrails;
1149 self
1150 }
1151
1152 pub fn output_guardrails(mut self, guardrails: GuardrailSet) -> Self {
1161 self.output_guardrails = guardrails;
1162 self
1163 }
1164
1165 pub fn tool_guardrails(mut self, guardrails: ToolGuardrailSet) -> Self {
1191 self.tool_guardrails = guardrails;
1192 self
1193 }
1194
1195 #[cfg(feature = "enhanced-plugins")]
1215 pub fn enhanced_plugin(mut self, plugin: Arc<dyn EnhancedPlugin>) -> Self {
1216 self.enhanced_plugins.push(plugin);
1217 self
1218 }
1219
1220 #[cfg(feature = "enhanced-plugins")]
1242 pub fn enhanced_plugins(mut self, plugins: Vec<Arc<dyn EnhancedPlugin>>) -> Self {
1243 self.enhanced_plugins.extend(plugins);
1244 self
1245 }
1246
1247 #[cfg(feature = "sandbox")]
1282 pub fn sandbox_config(mut self, config: adk_sandbox::workspace::SandboxConfig) -> Self {
1283 self.sandbox_config = Some(config);
1284 self
1285 }
1286
1287 pub fn build(self) -> Result<LlmAgent> {
1289 let model = self.model.ok_or_else(|| adk_core::AdkError::agent("Model is required"))?;
1290
1291 let mut seen_names = std::collections::HashSet::new();
1292 for agent in &self.sub_agents {
1293 if !seen_names.insert(agent.name()) {
1294 return Err(adk_core::AdkError::agent(format!(
1295 "Duplicate sub-agent name: {}",
1296 agent.name()
1297 )));
1298 }
1299 }
1300
1301 #[cfg(feature = "sandbox")]
1304 if let Some(ref sandbox_cfg) = self.sandbox_config {
1305 use adk_sandbox::workspace::Capability;
1306 if model.uses_interactions_api()
1307 && (sandbox_cfg.capabilities.contains(&Capability::Shell)
1308 || sandbox_cfg.capabilities.contains(&Capability::Filesystem))
1309 {
1310 return Err(adk_core::AdkError::new(
1311 adk_core::ErrorComponent::Agent,
1312 adk_core::ErrorCategory::InvalidInput,
1313 "code.gemini_interactions_conflict",
1314 "Cannot combine Gemini Interactions API (server-managed environment) \
1315 with client-side sandbox tools (Shell/Filesystem). These provide \
1316 competing filesystems and would produce nondeterministic behavior. \
1317 Either disable use_interactions_api or remove sandbox capabilities.",
1318 ));
1319 }
1320 }
1321
1322 #[cfg(feature = "enhanced-plugins")]
1324 let enhanced_plugin_manager = if self.enhanced_plugins.is_empty() {
1325 None
1326 } else {
1327 Some(Arc::new(EnhancedPluginManager::new(self.enhanced_plugins)))
1328 };
1329
1330 Ok(LlmAgent {
1331 name: self.name,
1332 description: self.description.unwrap_or_default(),
1333 model,
1334 instruction: self.instruction,
1335 instruction_provider: self.instruction_provider,
1336 global_instruction: self.global_instruction,
1337 global_instruction_provider: self.global_instruction_provider,
1338 skills_index: self.skills_index,
1339 skill_policy: self.skill_policy,
1340 max_skill_chars: self.max_skill_chars,
1341 input_schema: self.input_schema,
1342 output_schema: self.output_schema,
1343 output_max_retries: self.output_max_retries,
1344 disallow_transfer_to_parent: self.disallow_transfer_to_parent,
1345 disallow_transfer_to_peers: self.disallow_transfer_to_peers,
1346 include_contents: self.include_contents,
1347 tools: self.tools,
1348 toolsets: self.toolsets,
1349 sub_agents: self.sub_agents,
1350 output_key: self.output_key,
1351 generate_content_config: self.generate_content_config,
1352 max_iterations: self.max_iterations,
1353 tool_timeout: self.tool_timeout,
1354 before_callbacks: Arc::new(self.before_callbacks),
1355 after_callbacks: Arc::new(self.after_callbacks),
1356 before_model_callbacks: Arc::new(self.before_model_callbacks),
1357 after_model_callbacks: Arc::new(self.after_model_callbacks),
1358 before_tool_callbacks: Arc::new(self.before_tool_callbacks),
1359 after_tool_callbacks: Arc::new(self.after_tool_callbacks),
1360 on_tool_error_callbacks: Arc::new(self.on_tool_error_callbacks),
1361 after_tool_callbacks_full: Arc::new(self.after_tool_callbacks_full),
1362 default_retry_budget: self.default_retry_budget,
1363 tool_retry_budgets: self.tool_retry_budgets,
1364 circuit_breaker_threshold: self.circuit_breaker_threshold,
1365 tool_confirmation_policy: self.tool_confirmation_policy,
1366 tool_execution_strategy: self.tool_execution_strategy,
1367 input_guardrails: Arc::new(self.input_guardrails),
1368 output_guardrails: Arc::new(self.output_guardrails),
1369 tool_guardrails: Arc::new(self.tool_guardrails),
1370 #[cfg(feature = "enhanced-plugins")]
1371 enhanced_plugin_manager,
1372 #[cfg(feature = "sandbox")]
1373 sandbox_config: self.sandbox_config,
1374 })
1375 }
1376}
1377
1378const TOOL_PROGRESS_CAPACITY: usize = 256;
1386
1387const TOOL_PROGRESS_SEND_TIMEOUT: std::time::Duration = std::time::Duration::from_millis(100);
1392
1393const TOOL_PROGRESS_MAX_CHUNK_BYTES: usize = 8 * 1024;
1395
1396const TOOL_PROGRESS_MAX_TOTAL_BYTES: usize = 1024 * 1024;
1398
1399const TOOL_PROGRESS_TRUNCATION_MARKER: &str = "[adk: tool progress truncated]";
1401
1402fn truncate_on_char_boundary(text: &str, max_bytes: usize) -> &str {
1404 if text.len() <= max_bytes {
1405 return text;
1406 }
1407 let mut end = max_bytes;
1408 while end > 0 && !text.is_char_boundary(end) {
1409 end -= 1;
1410 }
1411 &text[..end]
1412}
1413
1414struct AgentToolContext {
1415 parent_ctx: Arc<dyn InvocationContext>,
1416 function_call_id: String,
1417 tool_name: Option<String>,
1420 actions: Mutex<EventActions>,
1421 progress_tx: Option<tokio::sync::mpsc::Sender<Event>>,
1422 progress_bytes: std::sync::atomic::AtomicUsize,
1424 progress_truncated: std::sync::atomic::AtomicBool,
1426}
1427
1428impl AgentToolContext {
1429 fn new(parent_ctx: Arc<dyn InvocationContext>, function_call_id: String) -> Self {
1430 Self {
1431 parent_ctx,
1432 function_call_id,
1433 tool_name: None,
1434 actions: Mutex::new(EventActions::default()),
1435 progress_tx: None,
1436 progress_bytes: std::sync::atomic::AtomicUsize::new(0),
1437 progress_truncated: std::sync::atomic::AtomicBool::new(false),
1438 }
1439 }
1440
1441 fn with_tool_name(mut self, tool_name: impl Into<String>) -> Self {
1443 self.tool_name = Some(tool_name.into());
1444 self
1445 }
1446
1447 fn with_progress(mut self, tx: tokio::sync::mpsc::Sender<Event>) -> Self {
1450 self.progress_tx = Some(tx);
1451 self
1452 }
1453
1454 async fn request_secret(&self, name: &str, purpose: Option<&str>) -> Result<Option<String>> {
1459 let mut request = adk_core::SecretRequest::new(name)
1460 .with_identity(
1461 self.parent_ctx.app_name(),
1462 self.parent_ctx.user_id(),
1463 self.parent_ctx.session_id(),
1464 )
1465 .with_invocation_id(self.parent_ctx.invocation_id());
1466 if let Some(tool_name) = &self.tool_name {
1467 request = request.with_tool_name(tool_name);
1468 }
1469 if let Some(purpose) = purpose {
1470 request = request.with_purpose(purpose);
1471 }
1472 self.parent_ctx.get_secret_for(&request).await
1473 }
1474
1475 async fn forward_progress(
1482 &self,
1483 tx: &tokio::sync::mpsc::Sender<Event>,
1484 stream: &str,
1485 chunk: &str,
1486 ) {
1487 use std::sync::atomic::Ordering;
1488
1489 if self.progress_truncated.load(Ordering::Relaxed) {
1490 return;
1491 }
1492
1493 let payload = truncate_on_char_boundary(chunk, TOOL_PROGRESS_MAX_CHUNK_BYTES);
1496 let forwarded = self.progress_bytes.fetch_add(payload.len(), Ordering::Relaxed);
1497 if forwarded.saturating_add(payload.len()) > TOOL_PROGRESS_MAX_TOTAL_BYTES {
1498 self.mark_progress_truncated(tx, stream).await;
1499 return;
1500 }
1501
1502 let event = Event::tool_progress(
1503 self.parent_ctx.invocation_id(),
1504 self.parent_ctx.agent_name(),
1505 &self.function_call_id,
1506 stream,
1507 payload,
1508 );
1509
1510 match tx.try_send(event) {
1511 Ok(()) => {}
1512 Err(tokio::sync::mpsc::error::TrySendError::Full(event)) => {
1513 match tokio::time::timeout(TOOL_PROGRESS_SEND_TIMEOUT, tx.send(event)).await {
1516 Ok(Ok(())) => {}
1517 Ok(Err(_)) => {}
1518 Err(_) => self.mark_progress_truncated(tx, stream).await,
1519 }
1520 }
1521 Err(tokio::sync::mpsc::error::TrySendError::Closed(_)) => {}
1522 }
1523 }
1524
1525 async fn mark_progress_truncated(&self, tx: &tokio::sync::mpsc::Sender<Event>, stream: &str) {
1527 use std::sync::atomic::Ordering;
1528 if self.progress_truncated.swap(true, Ordering::Relaxed) {
1529 return;
1530 }
1531 tracing::warn!(
1532 function_call.id = %self.function_call_id,
1533 progress.stream = %stream,
1534 "tool progress exceeded its budget, remaining output is not forwarded"
1535 );
1536 let marker = Event::tool_progress(
1537 self.parent_ctx.invocation_id(),
1538 self.parent_ctx.agent_name(),
1539 &self.function_call_id,
1540 stream,
1541 TOOL_PROGRESS_TRUNCATION_MARKER,
1542 );
1543 let _ = tx.try_send(marker);
1544 }
1545
1546 fn actions_guard(&self) -> std::sync::MutexGuard<'_, EventActions> {
1547 self.actions.lock().unwrap_or_else(|e| e.into_inner())
1548 }
1549}
1550
1551#[async_trait]
1552impl ReadonlyContext for AgentToolContext {
1553 fn invocation_id(&self) -> &str {
1554 self.parent_ctx.invocation_id()
1555 }
1556
1557 fn agent_name(&self) -> &str {
1558 self.parent_ctx.agent_name()
1559 }
1560
1561 fn user_id(&self) -> &str {
1562 self.parent_ctx.user_id()
1564 }
1565
1566 fn app_name(&self) -> &str {
1567 self.parent_ctx.app_name()
1569 }
1570
1571 fn session_id(&self) -> &str {
1572 self.parent_ctx.session_id()
1574 }
1575
1576 fn branch(&self) -> &str {
1577 self.parent_ctx.branch()
1578 }
1579
1580 fn user_content(&self) -> &Content {
1581 self.parent_ctx.user_content()
1582 }
1583}
1584
1585#[async_trait]
1586impl CallbackContext for AgentToolContext {
1587 fn artifacts(&self) -> Option<Arc<dyn adk_core::Artifacts>> {
1588 self.parent_ctx.artifacts()
1590 }
1591
1592 fn shared_state(&self) -> Option<Arc<adk_core::SharedState>> {
1593 self.parent_ctx.shared_state()
1594 }
1595}
1596
1597#[async_trait]
1598impl ToolContext for AgentToolContext {
1599 fn function_call_id(&self) -> &str {
1600 &self.function_call_id
1601 }
1602
1603 fn actions(&self) -> EventActions {
1604 self.actions_guard().clone()
1605 }
1606
1607 fn set_actions(&self, actions: EventActions) {
1608 *self.actions_guard() = actions;
1609 }
1610
1611 async fn search_memory(&self, query: &str) -> Result<Vec<MemoryEntry>> {
1612 if let Some(memory) = self.parent_ctx.memory() {
1614 memory.search(query).await
1615 } else {
1616 Ok(vec![])
1617 }
1618 }
1619
1620 fn memory(&self) -> Option<Arc<dyn adk_core::Memory>> {
1621 self.parent_ctx.memory()
1622 }
1623
1624 fn session(&self) -> Option<&dyn adk_core::Session> {
1625 Some(self.parent_ctx.session())
1626 }
1627
1628 fn run_config(&self) -> Option<&adk_core::RunConfig> {
1629 Some(self.parent_ctx.run_config())
1630 }
1631
1632 fn is_cancelled(&self) -> bool {
1633 self.parent_ctx.is_cancelled()
1634 }
1635
1636 fn request_metadata(&self) -> HashMap<String, serde_json::Value> {
1637 self.parent_ctx.request_metadata()
1638 }
1639
1640 fn delegation_depth(&self) -> u32 {
1641 self.parent_ctx.delegation_depth()
1642 }
1643
1644 fn max_delegation_depth(&self) -> Option<u32> {
1645 self.parent_ctx.max_delegation_depth()
1646 }
1647
1648 fn orchestration_root_invocation_id(&self) -> &str {
1649 self.parent_ctx.orchestration_root_invocation_id()
1650 }
1651
1652 fn orchestration_edge_id(&self) -> Option<&str> {
1653 self.parent_ctx.orchestration_edge_id()
1654 }
1655
1656 async fn emit_event(&self, event: Event) {
1657 if let Some(tx) = &self.progress_tx
1658 && !tx.is_closed()
1659 {
1660 let _ = tokio::time::timeout(TOOL_PROGRESS_SEND_TIMEOUT, tx.send(event)).await;
1661 }
1662 }
1663
1664 fn user_scopes(&self) -> Vec<String> {
1665 self.parent_ctx.user_scopes()
1666 }
1667
1668 async fn get_secret(&self, name: &str) -> Result<Option<String>> {
1669 self.request_secret(name, None).await
1670 }
1671
1672 async fn get_secret_for_purpose(&self, name: &str, purpose: &str) -> Result<Option<String>> {
1673 self.request_secret(name, Some(purpose)).await
1674 }
1675
1676 async fn emit_progress(&self, stream: &str, chunk: &str) {
1677 if let Some(tx) = &self.progress_tx {
1680 if !tx.is_closed() {
1682 self.forward_progress(tx, stream, chunk).await;
1683 }
1684 }
1685 tracing::debug!(
1687 target: "adk_agent::tool_progress",
1688 tool_call_id = %self.function_call_id,
1689 stream = %stream,
1690 "{chunk}",
1691 );
1692 }
1693}
1694
1695struct ToolOutcomeCallbackContext {
1699 inner: Arc<dyn CallbackContext>,
1700 outcome: ToolOutcome,
1701}
1702
1703#[async_trait]
1704impl ReadonlyContext for ToolOutcomeCallbackContext {
1705 fn invocation_id(&self) -> &str {
1706 self.inner.invocation_id()
1707 }
1708
1709 fn agent_name(&self) -> &str {
1710 self.inner.agent_name()
1711 }
1712
1713 fn user_id(&self) -> &str {
1714 self.inner.user_id()
1715 }
1716
1717 fn app_name(&self) -> &str {
1718 self.inner.app_name()
1719 }
1720
1721 fn session_id(&self) -> &str {
1722 self.inner.session_id()
1723 }
1724
1725 fn branch(&self) -> &str {
1726 self.inner.branch()
1727 }
1728
1729 fn user_content(&self) -> &Content {
1730 self.inner.user_content()
1731 }
1732}
1733
1734#[async_trait]
1735impl CallbackContext for ToolOutcomeCallbackContext {
1736 fn artifacts(&self) -> Option<Arc<dyn adk_core::Artifacts>> {
1737 self.inner.artifacts()
1738 }
1739
1740 fn tool_outcome(&self) -> Option<ToolOutcome> {
1741 Some(self.outcome.clone())
1742 }
1743}
1744
1745struct CircuitBreakerState {
1755 threshold: u32,
1756 failures: std::collections::HashMap<String, u32>,
1758}
1759
1760impl CircuitBreakerState {
1761 fn new(threshold: u32) -> Self {
1762 Self { threshold, failures: std::collections::HashMap::new() }
1763 }
1764
1765 fn is_open(&self, tool_name: &str) -> bool {
1767 self.failures.get(tool_name).copied().unwrap_or(0) >= self.threshold
1768 }
1769
1770 fn record(&mut self, outcome: &ToolOutcome) {
1772 if outcome.success {
1773 self.failures.remove(&outcome.tool_name);
1774 } else {
1775 let count = self.failures.entry(outcome.tool_name.clone()).or_insert(0);
1776 *count += 1;
1777 }
1778 }
1779}
1780
1781struct ToolExecutionResult {
1782 index: usize,
1783 content: Content,
1784 actions: EventActions,
1785 escalate_or_skip: bool,
1786}
1787
1788struct ToolExecutor<'a> {
1789 ctx: Arc<dyn InvocationContext>,
1790 tool_map: &'a std::collections::HashMap<String, Arc<dyn Tool>>,
1791 tool_retry_budgets: &'a std::collections::HashMap<String, RetryBudget>,
1792 default_retry_budget: &'a Option<RetryBudget>,
1793 before_tool_callbacks: &'a Arc<Vec<BeforeToolCallback>>,
1794 after_tool_callbacks: &'a Arc<Vec<AfterToolCallback>>,
1795 after_tool_callbacks_full: &'a Arc<Vec<AfterToolCallbackFull>>,
1796 on_tool_error_callbacks: &'a Arc<Vec<OnToolErrorCallback>>,
1797 tool_confirmation_policy: &'a ToolConfirmationPolicy,
1798 cb_mutex: &'a std::sync::Mutex<Option<CircuitBreakerState>>,
1799 invocation_id: &'a str,
1800 concurrency_manager: &'a adk_core::ToolConcurrencyManager,
1801 progress_tx: tokio::sync::mpsc::Sender<Event>,
1802 tool_timeout: std::time::Duration,
1803 confirmation_decisions: &'a std::collections::HashMap<String, ToolConfirmationDecision>,
1804 confirmation_fingerprints: &'a std::collections::HashMap<String, String>,
1805 live_confirmation_decisions: &'a std::collections::HashMap<String, ToolConfirmationDecision>,
1806 #[cfg(feature = "enhanced-plugins")]
1807 enhanced_plugin_manager: &'a Option<Arc<EnhancedPluginManager>>,
1808}
1809
1810impl ToolExecutor<'_> {
1811 async fn execute(&self, call: PendingToolCall) -> ToolExecutionResult {
1812 let PendingToolCall { index, name, args, id, function_call_id, guardrail_denial } = call;
1813 let mut tool_actions = EventActions::default();
1814 let mut response_content: Option<Content> = None;
1815 let mut run_after_tool_callbacks = true;
1816 let mut tool_outcome_for_callback: Option<ToolOutcome> = None;
1817 let mut executed_tool: Option<Arc<dyn Tool>> = None;
1818 let mut executed_tool_response: Option<serde_json::Value> = None;
1819
1820 if let Some(reason) = guardrail_denial {
1821 let denied_content = Content {
1824 role: "function".to_string(),
1825 parts: vec![Part::FunctionResponse {
1826 function_response: FunctionResponseData::new(
1827 name.clone(),
1828 serde_json::json!({ "error": reason }),
1829 ),
1830 id: id.clone(),
1831 annotations: None,
1832 }],
1833 };
1834 return ToolExecutionResult {
1835 index,
1836 content: denied_content,
1837 actions: tool_actions,
1838 escalate_or_skip: false,
1839 };
1840 }
1841
1842 let _concurrency_permit = match self.concurrency_manager.acquire(&name).await {
1846 Ok(permit) => Some(permit),
1847 Err(e) => {
1848 let error_content = Content {
1850 role: "function".to_string(),
1851 parts: vec![Part::FunctionResponse {
1852 function_response: FunctionResponseData::new(
1853 name.clone(),
1854 serde_json::json!({ "error": e.to_string() }),
1855 ),
1856 id: id.clone(),
1857 annotations: None,
1858 }],
1859 };
1860 return ToolExecutionResult {
1861 index,
1862 content: error_content,
1863 actions: tool_actions,
1864 escalate_or_skip: false,
1865 };
1866 }
1867 };
1868
1869 if self.tool_confirmation_policy.requires_confirmation(&name) {
1871 match self.live_confirmation_decisions.get(&function_call_id).copied().or_else(|| {
1872 static_confirmation_decision(
1873 self.confirmation_decisions,
1874 self.confirmation_fingerprints,
1875 &function_call_id,
1876 &name,
1877 &args,
1878 )
1879 }) {
1880 Some(ToolConfirmationDecision::Approve) => {
1881 tool_actions.tool_confirmation_decision =
1882 Some(ToolConfirmationDecision::Approve);
1883 }
1884 Some(ToolConfirmationDecision::Deny) => {
1885 tool_actions.tool_confirmation_decision = Some(ToolConfirmationDecision::Deny);
1886 response_content = Some(Content {
1887 role: "function".to_string(),
1888 parts: vec![Part::FunctionResponse {
1889 function_response: FunctionResponseData::new(
1890 name.clone(),
1891 serde_json::json!({
1892 "error": format!("Tool '{}' execution denied by confirmation policy", name)
1893 }),
1894 ),
1895 id: id.clone(),
1896 annotations: None,
1897 }],
1898 });
1899 run_after_tool_callbacks = false;
1900 }
1901 None => {
1902 response_content = Some(Content {
1903 role: "function".to_string(),
1904 parts: vec![Part::FunctionResponse {
1905 function_response: FunctionResponseData::new(
1906 name.clone(),
1907 serde_json::json!({
1908 "error": format!("Tool '{}' requires confirmation", name)
1909 }),
1910 ),
1911 id: id.clone(),
1912 annotations: None,
1913 }],
1914 });
1915 run_after_tool_callbacks = false;
1916 }
1917 }
1918 }
1919
1920 #[allow(unused_mut)]
1923 let mut final_args = args.clone();
1924
1925 #[cfg(feature = "enhanced-plugins")]
1927 if response_content.is_none()
1928 && let Some(epm) = self.enhanced_plugin_manager.as_ref()
1929 && let Some(tool_ref) = self.tool_map.get(&name)
1930 {
1931 match epm
1932 .run_before_tool_call(
1933 tool_ref.clone(),
1934 final_args.clone(),
1935 self.ctx.clone() as Arc<dyn CallbackContext>,
1936 )
1937 .await
1938 {
1939 Ok(BeforeToolCallResult::Continue(modified_args)) => {
1940 final_args = modified_args;
1941 }
1942 Ok(BeforeToolCallResult::ShortCircuit(synthetic_result)) => {
1943 response_content = Some(Content {
1945 role: "function".to_string(),
1946 parts: vec![Part::FunctionResponse {
1947 function_response: FunctionResponseData::from_tool_result(
1948 name.clone(),
1949 synthetic_result,
1950 ),
1951 id: id.clone(),
1952 annotations: None,
1953 }],
1954 });
1955 executed_tool = Some(tool_ref.clone());
1956 }
1957 Err(e) => {
1958 response_content = Some(Content {
1959 role: "function".to_string(),
1960 parts: vec![Part::FunctionResponse {
1961 function_response: FunctionResponseData::new(
1962 name.clone(),
1963 serde_json::json!({ "error": e.to_string() }),
1964 ),
1965 id: id.clone(),
1966 annotations: None,
1967 }],
1968 });
1969 run_after_tool_callbacks = false;
1970 }
1971 }
1972 }
1973
1974 if response_content.is_none() {
1975 let tool_ctx = Arc::new(ToolCallbackContext::new(
1976 self.ctx.clone(),
1977 name.clone(),
1978 final_args.clone(),
1979 ));
1980 for callback in self.before_tool_callbacks.as_ref() {
1981 match callback(tool_ctx.clone() as Arc<dyn CallbackContext>).await {
1982 Ok(Some(c)) => {
1983 response_content = Some(c);
1984 break;
1985 }
1986 Ok(None) => continue,
1987 Err(e) => {
1988 response_content = Some(Content {
1989 role: "function".to_string(),
1990 parts: vec![Part::FunctionResponse {
1991 function_response: FunctionResponseData::new(
1992 name.clone(),
1993 serde_json::json!({ "error": e.to_string() }),
1994 ),
1995 id: id.clone(),
1996 annotations: None,
1997 }],
1998 });
1999 run_after_tool_callbacks = false;
2000 break;
2001 }
2002 }
2003 }
2004 }
2005
2006 if response_content.is_none() {
2008 let guard = self.cb_mutex.lock().unwrap_or_else(|e| e.into_inner());
2009 if let Some(ref cb_state) = *guard
2010 && cb_state.is_open(&name)
2011 {
2012 let msg = format!(
2013 "Tool '{}' is temporarily disabled after {} consecutive failures",
2014 name, cb_state.threshold
2015 );
2016 tracing::warn!(tool.name = %name, "circuit breaker open, skipping tool execution");
2017 response_content = Some(Content {
2018 role: "function".to_string(),
2019 parts: vec![Part::FunctionResponse {
2020 function_response: FunctionResponseData::new(
2021 name.clone(),
2022 serde_json::json!({ "error": msg }),
2023 ),
2024 id: id.clone(),
2025 annotations: None,
2026 }],
2027 });
2028 run_after_tool_callbacks = false;
2029 }
2030 drop(guard);
2031 }
2032
2033 if response_content.is_none() {
2035 if let Some(tool) = self.tool_map.get(&name) {
2036 let tool_ctx: Arc<dyn ToolContext> = Arc::new(
2037 AgentToolContext::new(self.ctx.clone(), function_call_id.clone())
2038 .with_tool_name(tool.name())
2039 .with_progress(self.progress_tx.clone()),
2040 );
2041 let span_name = format!("execute_tool {name}");
2042 let tool_span = tracing::info_span!(
2043 "",
2044 otel.name = %span_name,
2045 tool.name = %name,
2046 "gcp.vertex.agent.event_id" = %format!("{}_{}", self.invocation_id, name),
2047 "gcp.vertex.agent.invocation_id" = %self.invocation_id,
2048 "gcp.vertex.agent.session_id" = %self.ctx.session_id(),
2049 "gen_ai.conversation.id" = %self.ctx.session_id()
2050 );
2051
2052 let budget =
2053 self.tool_retry_budgets.get(&name).or(self.default_retry_budget.as_ref());
2054 let max_attempts = budget.map(|b| b.max_retries + 1).unwrap_or(1);
2055 let retry_delay = budget.map(|b| b.delay).unwrap_or_default();
2056
2057 let tool_clone = tool.clone();
2058 let tool_start = std::time::Instant::now();
2059 let mut last_error = String::new();
2060 let mut final_attempt: u32 = 0;
2061 let mut retry_result: Option<serde_json::Value> = None;
2062
2063 for attempt in 0..max_attempts {
2064 final_attempt = attempt;
2065 if attempt > 0 {
2066 tokio::time::sleep(retry_delay).await;
2067 }
2068 match async {
2069 let args_payload = trace_json_payload(
2070 &final_args,
2071 self.ctx.run_config().record_payloads,
2072 self.ctx.run_config().trace_payload_max_bytes,
2073 );
2074 tracing::debug!(tool.name = %name, tool.args = %args_payload, attempt = attempt, "tool_call");
2075 let exec_future = tool_clone.execute(tool_ctx.clone(), final_args.clone());
2076 let unwind_safe_future = std::panic::AssertUnwindSafe(
2077 tokio::time::timeout(self.tool_timeout, exec_future),
2078 );
2079 match futures::FutureExt::catch_unwind(unwind_safe_future).await {
2080 Ok(result) => result,
2081 Err(_panic) => Ok(Err(adk_core::AdkError::tool(format!(
2082 "tool '{}' panicked during execution",
2083 name
2084 )))),
2085 }
2086 }
2087 .instrument(tool_span.clone())
2088 .await
2089 {
2090 Ok(Ok(value)) => {
2091 let result_payload = trace_json_payload(
2092 &value,
2093 self.ctx.run_config().record_payloads,
2094 self.ctx.run_config().trace_payload_max_bytes,
2095 );
2096 tracing::debug!(tool.name = %name, tool.result = %result_payload, "tool_result");
2097 retry_result = Some(value);
2098 break;
2099 }
2100 Ok(Err(e)) => {
2101 last_error = e.to_string();
2102 if attempt + 1 < max_attempts {
2103 tracing::warn!(tool.name = %name, attempt = attempt, error = %last_error, "tool execution failed, retrying");
2104 } else {
2105 tracing::warn!(tool.name = %name, error = %last_error, "tool_error");
2106 }
2107 }
2108 Err(_) => {
2109 last_error = format!(
2110 "Tool '{}' timed out after {} seconds",
2111 name,
2112 self.tool_timeout.as_secs()
2113 );
2114 if attempt + 1 < max_attempts {
2115 tracing::warn!(tool.name = %name, attempt = attempt, timeout_secs = self.tool_timeout.as_secs(), "tool timed out, retrying");
2116 } else {
2117 tracing::warn!(tool.name = %name, timeout_secs = self.tool_timeout.as_secs(), "tool_timeout");
2118 }
2119 }
2120 }
2121 }
2122
2123 let tool_duration = tool_start.elapsed();
2124 let (tool_success, tool_error_message, function_response) = match retry_result {
2125 Some(value) => (true, None, value),
2126 None => (
2127 false,
2128 Some(last_error.clone()),
2129 serde_json::json!({ "error": last_error }),
2130 ),
2131 };
2132
2133 let outcome = ToolOutcome {
2134 tool_name: name.clone(),
2135 tool_args: final_args.clone(),
2136 success: tool_success,
2137 duration: tool_duration,
2138 error_message: tool_error_message.clone(),
2139 attempt: final_attempt,
2140 };
2141 tool_outcome_for_callback = Some(outcome);
2142
2143 {
2145 let mut guard = self.cb_mutex.lock().unwrap_or_else(|e| e.into_inner());
2146 if let Some(ref mut cb_state) = *guard {
2147 cb_state.record(tool_outcome_for_callback.as_ref().unwrap());
2148 }
2149 }
2150
2151 let final_function_response = if !tool_success {
2153 let mut fallback_result = None;
2154 let error_msg = tool_error_message.clone().unwrap_or_default();
2155 for callback in self.on_tool_error_callbacks.as_ref() {
2156 match callback(
2157 self.ctx.clone() as Arc<dyn CallbackContext>,
2158 tool.clone(),
2159 final_args.clone(),
2160 error_msg.clone(),
2161 )
2162 .await
2163 {
2164 Ok(Some(result)) => {
2165 fallback_result = Some(result);
2166 break;
2167 }
2168 Ok(None) => continue,
2169 Err(e) => {
2170 tracing::warn!(error = %e, "on_tool_error callback failed");
2171 break;
2172 }
2173 }
2174 }
2175 fallback_result.unwrap_or(function_response)
2176 } else {
2177 function_response
2178 };
2179
2180 let confirmation_decision = tool_actions.tool_confirmation_decision;
2181 tool_actions = tool_ctx.actions();
2182 if tool_actions.tool_confirmation_decision.is_none() {
2183 tool_actions.tool_confirmation_decision = confirmation_decision;
2184 }
2185 executed_tool = Some(tool.clone());
2186 executed_tool_response = Some(final_function_response.clone());
2187 response_content = Some(Content {
2188 role: "function".to_string(),
2189 parts: vec![Part::FunctionResponse {
2190 function_response: FunctionResponseData::from_tool_result(
2191 name.clone(),
2192 final_function_response,
2193 ),
2194 id: id.clone(),
2195 annotations: None,
2196 }],
2197 });
2198 } else {
2199 response_content = Some(Content {
2200 role: "function".to_string(),
2201 parts: vec![Part::FunctionResponse {
2202 function_response: FunctionResponseData::new(
2203 name.clone(),
2204 serde_json::json!({
2205 "error": format!("Tool {} not found", name)
2206 }),
2207 ),
2208 id: id.clone(),
2209 annotations: None,
2210 }],
2211 });
2212 }
2213 }
2214
2215 let mut response_content = response_content.expect("tool response content is set");
2217 if run_after_tool_callbacks {
2218 let outcome_ctx: Arc<dyn CallbackContext> = match tool_outcome_for_callback {
2219 Some(outcome) => Arc::new(ToolOutcomeCallbackContext {
2220 inner: self.ctx.clone() as Arc<dyn CallbackContext>,
2221 outcome,
2222 }),
2223 None => self.ctx.clone() as Arc<dyn CallbackContext>,
2224 };
2225 let cb_ctx: Arc<dyn CallbackContext> =
2226 Arc::new(ToolCallbackContext::new(outcome_ctx, name.clone(), final_args.clone()));
2227 for callback in self.after_tool_callbacks.as_ref() {
2228 match callback(cb_ctx.clone()).await {
2229 Ok(Some(modified)) => {
2230 response_content = modified;
2231 break;
2232 }
2233 Ok(None) => continue,
2234 Err(e) => {
2235 response_content = Content {
2236 role: "function".to_string(),
2237 parts: vec![Part::FunctionResponse {
2238 function_response: FunctionResponseData::new(
2239 name.clone(),
2240 serde_json::json!({ "error": e.to_string() }),
2241 ),
2242 id: id.clone(),
2243 annotations: None,
2244 }],
2245 };
2246 break;
2247 }
2248 }
2249 }
2250 if let (Some(tool_ref), Some(tool_resp)) = (&executed_tool, executed_tool_response) {
2251 for callback in self.after_tool_callbacks_full.as_ref() {
2252 match callback(
2253 cb_ctx.clone(),
2254 tool_ref.clone(),
2255 final_args.clone(),
2256 tool_resp.clone(),
2257 )
2258 .await
2259 {
2260 Ok(Some(modified_value)) => {
2261 response_content = Content {
2262 role: "function".to_string(),
2263 parts: vec![Part::FunctionResponse {
2264 function_response: FunctionResponseData::from_tool_result(
2265 name.clone(),
2266 modified_value,
2267 ),
2268 id: id.clone(),
2269 annotations: None,
2270 }],
2271 };
2272 break;
2273 }
2274 Ok(None) => continue,
2275 Err(e) => {
2276 response_content = Content {
2277 role: "function".to_string(),
2278 parts: vec![Part::FunctionResponse {
2279 function_response: FunctionResponseData::new(
2280 name.clone(),
2281 serde_json::json!({ "error": e.to_string() }),
2282 ),
2283 id: id.clone(),
2284 annotations: None,
2285 }],
2286 };
2287 break;
2288 }
2289 }
2290 }
2291 }
2292
2293 #[cfg(feature = "enhanced-plugins")]
2296 if let Some(epm) = self.enhanced_plugin_manager.as_ref()
2297 && let Some(tool_ref) = &executed_tool
2298 {
2299 let result_value = response_content
2301 .parts
2302 .iter()
2303 .find_map(|p| {
2304 if let Part::FunctionResponse { function_response, .. } = p {
2305 Some(function_response.response.clone())
2306 } else {
2307 None
2308 }
2309 })
2310 .unwrap_or(serde_json::json!(null));
2311
2312 match epm
2313 .run_after_tool_call(
2314 tool_ref.clone(),
2315 &final_args,
2316 result_value,
2317 self.ctx.clone() as Arc<dyn CallbackContext>,
2318 )
2319 .await
2320 {
2321 Ok(adk_plugin::AfterToolCallResult::Continue(modified_result)) => {
2322 response_content = Content {
2323 role: "function".to_string(),
2324 parts: vec![Part::FunctionResponse {
2325 function_response: FunctionResponseData::from_tool_result(
2326 name.clone(),
2327 modified_result,
2328 ),
2329 id: id.clone(),
2330 annotations: None,
2331 }],
2332 };
2333 }
2334 Err(e) => {
2335 response_content = Content {
2336 role: "function".to_string(),
2337 parts: vec![Part::FunctionResponse {
2338 function_response: FunctionResponseData::new(
2339 name.clone(),
2340 serde_json::json!({ "error": e.to_string() }),
2341 ),
2342 id: id.clone(),
2343 annotations: None,
2344 }],
2345 };
2346 }
2347 }
2348 }
2349 }
2350
2351 let escalate_or_skip = tool_actions.escalate || tool_actions.skip_summarization;
2352 ToolExecutionResult {
2353 index,
2354 content: response_content,
2355 actions: tool_actions,
2356 escalate_or_skip,
2357 }
2358 }
2359}
2360
2361#[async_trait]
2362impl Agent for LlmAgent {
2363 fn name(&self) -> &str {
2364 &self.name
2365 }
2366
2367 fn description(&self) -> &str {
2368 &self.description
2369 }
2370
2371 fn sub_agents(&self) -> &[Arc<dyn Agent>] {
2372 &self.sub_agents
2373 }
2374
2375 fn capabilities(&self) -> adk_core::AgentCapabilities {
2376 adk_core::AgentCapabilities {
2377 runtime_tools: true,
2378 handoff: true,
2379 relationship_confirmation: true,
2380 checkpoint_resume: false,
2381 shared_state: true,
2382 invocation_metadata: true,
2383 }
2384 }
2385
2386 #[adk_telemetry::instrument(
2387 skip(self, ctx),
2388 fields(
2389 agent.name = %self.name,
2390 agent.description = %self.description,
2391 invocation.id = %ctx.invocation_id(),
2392 user.id = %ctx.user_id(),
2393 session.id = %ctx.session_id()
2394 )
2395 )]
2396 async fn run(&self, ctx: Arc<dyn InvocationContext>) -> Result<adk_core::EventStream> {
2397 adk_telemetry::info!("Starting agent execution");
2398 let ctx = Self::apply_input_guardrails(ctx, self.input_guardrails.clone()).await?;
2399
2400 let agent_name = self.name.clone();
2401 let invocation_id = ctx.invocation_id().to_string();
2402 let model = self.model.clone();
2403 let prompt_config = PromptConfig::from_agent(self);
2404 let tool_setup = ToolSetup::from_agent(self);
2405 let output_key = self.output_key.clone();
2406 let output_max_retries = self.output_max_retries;
2407 let generate_content_config = self.generate_content_config.clone();
2408 let max_iterations = self.max_iterations;
2409 let tool_timeout = self.tool_timeout;
2410 let before_agent_callbacks = self.before_callbacks.clone();
2412 let after_agent_callbacks = self.after_callbacks.clone();
2413 let before_model_callbacks = self.before_model_callbacks.clone();
2414 let after_model_callbacks = self.after_model_callbacks.clone();
2415 let before_tool_callbacks = self.before_tool_callbacks.clone();
2416 let after_tool_callbacks = self.after_tool_callbacks.clone();
2417 let on_tool_error_callbacks = self.on_tool_error_callbacks.clone();
2418 let after_tool_callbacks_full = self.after_tool_callbacks_full.clone();
2419 let default_retry_budget = self.default_retry_budget.clone();
2420 let tool_retry_budgets = self.tool_retry_budgets.clone();
2421 let circuit_breaker_threshold = self.circuit_breaker_threshold;
2422 let tool_confirmation_policy = self.tool_confirmation_policy.clone();
2423 let tool_guardrails = Arc::clone(&self.tool_guardrails);
2424 let output_guardrails = self.output_guardrails.clone();
2425 let agent_tool_execution_strategy = self.tool_execution_strategy;
2426 #[cfg(feature = "enhanced-plugins")]
2427 let enhanced_plugin_manager = self.enhanced_plugin_manager.clone();
2428
2429 let s = stream! {
2430 let confirmation_decisions =
2431 ctx.run_config().tool_confirmation_decisions.clone();
2432 let confirmation_fingerprints =
2433 ctx.run_config().tool_confirmation_fingerprints.clone();
2434 let mut live_confirmation_decisions =
2435 std::collections::HashMap::<String, ToolConfirmationDecision>::new();
2436 let confirmation_handler = ctx.run_config().tool_confirmation_handler.clone();
2437
2438 for callback in before_agent_callbacks.as_ref() {
2442 match callback(ctx.clone() as Arc<dyn CallbackContext>).await {
2443 Ok(Some(content)) => {
2444 let mut early_event = Event::new(&invocation_id);
2446 early_event.author = agent_name.clone();
2447 early_event.llm_response.content = Some(content);
2448 yield Ok(early_event);
2449
2450 for after_callback in after_agent_callbacks.as_ref() {
2452 match after_callback(ctx.clone() as Arc<dyn CallbackContext>).await {
2453 Ok(Some(after_content)) => {
2454 let mut after_event = Event::new(&invocation_id);
2455 after_event.author = agent_name.clone();
2456 after_event.llm_response.content = Some(after_content);
2457 yield Ok(after_event);
2458 return;
2459 }
2460 Ok(None) => continue,
2461 Err(e) => {
2462 yield Err(e);
2463 return;
2464 }
2465 }
2466 }
2467 return;
2468 }
2469 Ok(None) => {
2470 continue;
2472 }
2473 Err(e) => {
2474 yield Err(e);
2476 return;
2477 }
2478 }
2479 }
2480
2481 let mut conversation_history = match prompt_config
2483 .prepare_conversation(&ctx, &agent_name)
2484 .await
2485 {
2486 Ok(history) => history,
2487 Err(error) => {
2488 yield Err(error);
2489 return;
2490 }
2491 };
2492
2493 let resolved_tools = match tool_setup.resolve(&ctx).await {
2494 Ok(tools) => tools,
2495 Err(error) => {
2496 yield Err(error);
2497 return;
2498 }
2499 };
2500 let mut tool_map = resolved_tools.map;
2501 let mut tool_declarations = resolved_tools.declarations;
2502 let mut valid_transfer_targets = resolved_tools.transfer_targets;
2503
2504 let mut circuit_breaker_state = circuit_breaker_threshold.map(CircuitBreakerState::new);
2507
2508 let mut last_interaction_id: Option<String> = None;
2517
2518 let mut iteration = 0;
2520 let mut schema_retry_count: usize = 0;
2521
2522 loop {
2523 if ctx.is_cancelled() {
2526 tracing::info!(agent.name = %agent_name, "invocation cancelled — stopping agent loop");
2527 return;
2528 }
2529 iteration += 1;
2530 if iteration > max_iterations {
2531 yield Err(adk_core::AdkError::agent(
2532 format!("Max iterations ({max_iterations}) exceeded")
2533 ));
2534 return;
2535 }
2536
2537 let config = build_generation_config(
2538 generate_content_config.as_ref(),
2539 prompt_config.output_schema.as_ref(),
2540 ctx.run_config().cached_content.as_deref(),
2541 );
2542
2543 let request = LlmRequest {
2544 model: model.name().to_string(),
2545 contents: conversation_history.clone(),
2546 tools: tool_declarations.clone(),
2547 config,
2548 previous_response_id: last_interaction_id.clone(),
2555 };
2556
2557 #[cfg(feature = "enhanced-plugins")]
2561 let (request, model_response_override_from_plugin) = {
2562 if let Some(epm) = &enhanced_plugin_manager {
2563 match epm.run_before_model_call(request, ctx.clone() as Arc<dyn CallbackContext>).await {
2564 Ok(BeforeModelCallResult::Continue(modified_request)) => {
2565 (modified_request, None)
2566 }
2567 Ok(BeforeModelCallResult::ShortCircuit(response)) => {
2568 (LlmRequest::new("", vec![]), Some(response))
2570 }
2571 Err(e) => {
2572 yield Err(e);
2573 return;
2574 }
2575 }
2576 } else {
2577 (request, None)
2578 }
2579 };
2580 #[cfg(not(feature = "enhanced-plugins"))]
2581 let model_response_override_from_plugin: Option<LlmResponse> = None;
2582
2583 let mut current_request = request;
2586 let mut model_response_override = model_response_override_from_plugin;
2587 if model_response_override.is_none() {
2588 for callback in before_model_callbacks.as_ref() {
2589 match callback(ctx.clone() as Arc<dyn CallbackContext>, current_request.clone()).await {
2590 Ok(BeforeModelResult::Continue(modified_request)) => {
2591 current_request = modified_request;
2593 }
2594 Ok(BeforeModelResult::Skip(response)) => {
2595 model_response_override = Some(response);
2597 break;
2598 }
2599 Err(e) => {
2600 yield Err(e);
2602 return;
2603 }
2604 }
2605 }
2606 }
2607 let request = current_request;
2608
2609 let mut accumulated_content: Option<Content> = None;
2611 let mut final_provider_metadata: Option<serde_json::Value> = None;
2612
2613 if let Some(cached_response) = model_response_override {
2614 accumulated_content = cached_response.content.clone();
2617 final_provider_metadata = cached_response.provider_metadata.clone();
2618 normalize_option_content(&mut accumulated_content);
2619 if let Some(content) = accumulated_content.take() {
2620 let has_function_calls = content
2621 .parts
2622 .iter()
2623 .any(|part| matches!(part, Part::FunctionCall { .. }));
2624 let content = if has_function_calls {
2625 content
2626 } else {
2627 Self::apply_output_guardrails(output_guardrails.as_ref(), content).await?
2628 };
2629 accumulated_content = Some(content);
2630 }
2631
2632 let mut cached_event = Event::new(&invocation_id);
2633 cached_event.author = agent_name.clone();
2634 cached_event.llm_response.content = accumulated_content.clone();
2635 cached_event.llm_response.provider_metadata = cached_response.provider_metadata.clone();
2636 cached_event.llm_response.interaction_id = cached_response.interaction_id.clone();
2638 if cached_response.interaction_id.is_some() {
2639 last_interaction_id = cached_response.interaction_id.clone();
2640 }
2641 cached_event.llm_request = Some(serde_json::to_string(&request).unwrap_or_default());
2642 cached_event.provider_metadata.insert("gcp.vertex.agent.llm_request".to_string(), serde_json::to_string(&request).unwrap_or_default());
2643 cached_event.provider_metadata.insert("gcp.vertex.agent.llm_response".to_string(), serde_json::to_string(&cached_response).unwrap_or_default());
2644
2645 if let Some(ref content) = accumulated_content {
2647 cached_event.long_running_tool_ids =
2648 collect_long_running_tool_ids(&tool_map, content);
2649 }
2650
2651 yield Ok(cached_event);
2652 } else {
2653 let request_json = serde_json::to_string(&request).unwrap_or_default();
2655 let trace_request_json = trace_json_payload(
2656 &request,
2657 ctx.run_config().record_payloads,
2658 ctx.run_config().trace_payload_max_bytes,
2659 );
2660
2661 let llm_ts = std::time::SystemTime::now()
2663 .duration_since(std::time::UNIX_EPOCH)
2664 .unwrap_or_default()
2665 .as_nanos();
2666 let llm_event_id = format!("{}_llm_{}", invocation_id, llm_ts);
2667 let llm_span = tracing::info_span!(
2668 "call_llm",
2669 "gcp.vertex.agent.event_id" = %llm_event_id,
2670 "gcp.vertex.agent.invocation_id" = %invocation_id,
2671 "gcp.vertex.agent.session_id" = %ctx.session_id(),
2672 "gen_ai.conversation.id" = %ctx.session_id(),
2673 "gcp.vertex.agent.llm_request" = %trace_request_json,
2674 "gcp.vertex.agent.llm_response" = tracing::field::Empty );
2676 use adk_core::StreamingMode;
2689 let streaming_mode = ctx.run_config().streaming_mode;
2690 let should_stream_to_client = matches!(streaming_mode, StreamingMode::SSE | StreamingMode::Bidi)
2691 && output_guardrails.is_empty();
2692
2693 let mut response_stream = model
2695 .generate_content(request, true)
2696 .instrument(llm_span.clone())
2697 .await?;
2698
2699 use futures::StreamExt;
2700
2701 let mut last_chunk: Option<LlmResponse> = None;
2703
2704 while let Some(chunk_result) =
2706 response_stream.next().instrument(llm_span.clone()).await
2707 {
2708 if ctx.is_cancelled() {
2712 llm_span.in_scope(|| {
2713 tracing::info!(agent.name = %agent_name, "invocation cancelled during LLM streaming")
2714 });
2715 return;
2716 }
2717 let mut chunk = match chunk_result {
2718 Ok(c) => c,
2719 Err(e) => {
2720 yield Err(e);
2721 return;
2722 }
2723 };
2724
2725 for callback in after_model_callbacks.as_ref() {
2728 match callback(ctx.clone() as Arc<dyn CallbackContext>, chunk.clone())
2729 .instrument(llm_span.clone())
2730 .await
2731 {
2732 Ok(Some(modified_chunk)) => {
2733 chunk = modified_chunk;
2735 break;
2736 }
2737 Ok(None) => {
2738 continue;
2740 }
2741 Err(e) => {
2742 yield Err(e);
2744 return;
2745 }
2746 }
2747 }
2748
2749 normalize_option_content(&mut chunk.content);
2750
2751 if let Some(chunk_content) = chunk.content.clone() {
2753 if let Some(ref mut acc) = accumulated_content {
2754 acc.parts.extend(chunk_content.parts);
2755 } else {
2756 accumulated_content = Some(chunk_content);
2757 }
2758 }
2759
2760 if should_stream_to_client {
2762 let long_running_tool_ids = chunk
2763 .content
2764 .as_ref()
2765 .map(|content| collect_long_running_tool_ids(&tool_map, content))
2766 .unwrap_or_default();
2767 yield Ok(build_partial_llm_event(
2768 &llm_event_id,
2769 &invocation_id,
2770 &agent_name,
2771 &request_json,
2772 &chunk,
2773 long_running_tool_ids,
2774 ));
2775 }
2776
2777 if chunk.interaction_id.is_some() {
2781 last_interaction_id = chunk.interaction_id.clone();
2782 }
2783
2784 last_chunk = Some(chunk.clone());
2786
2787 if chunk.turn_complete {
2789 break;
2790 }
2791 }
2792
2793 if !should_stream_to_client {
2795 if let Some(content) = accumulated_content.take() {
2796 let has_function_calls = content
2797 .parts
2798 .iter()
2799 .any(|part| matches!(part, Part::FunctionCall { .. }));
2800 let content = if has_function_calls {
2801 content
2802 } else {
2803 Self::apply_output_guardrails(output_guardrails.as_ref(), content)
2804 .instrument(llm_span.clone())
2805 .await?
2806 };
2807 accumulated_content = Some(content);
2808 }
2809
2810 if let Some(last) = &last_chunk {
2811 final_provider_metadata = last.provider_metadata.clone();
2812 }
2813 let long_running_tool_ids = accumulated_content
2814 .as_ref()
2815 .map(|content| collect_long_running_tool_ids(&tool_map, content))
2816 .unwrap_or_default();
2817 yield Ok(build_final_llm_event(
2818 &llm_event_id,
2819 &invocation_id,
2820 &agent_name,
2821 &request_json,
2822 accumulated_content.as_ref(),
2823 last_chunk.as_ref(),
2824 long_running_tool_ids,
2825 ));
2826 }
2827
2828 if let Some(ref last) = last_chunk
2839 && let Some(ref code) = last.error_code
2840 {
2841 let message = last
2842 .error_message
2843 .clone()
2844 .unwrap_or_else(|| "provider reported a terminal error".to_string());
2845 llm_span.in_scope(|| {
2846 tracing::error!(
2847 error.code = %code,
2848 error.message = %message,
2849 agent = %agent_name,
2850 "model reported a terminal error"
2851 )
2852 });
2853 let mut details = adk_core::ErrorDetails::default();
2860 details
2861 .metadata
2862 .insert("provider_error_code".to_string(), serde_json::json!(code));
2863 let provider_error = adk_core::AdkError::new(
2864 adk_core::ErrorComponent::Model,
2865 adk_core::ErrorCategory::Internal,
2866 "model.provider_error",
2867 format!("{code}: {message}"),
2868 )
2869 .with_details(details);
2870 yield Err(provider_error);
2871 return;
2872 }
2873
2874 if let Some(ref content) = accumulated_content {
2876 let response_json = trace_json_payload(
2877 content,
2878 ctx.run_config().record_payloads,
2879 ctx.run_config().trace_payload_max_bytes,
2880 );
2881 llm_span.record("gcp.vertex.agent.llm_response", &response_json);
2882 }
2883 }
2884
2885 #[cfg(feature = "enhanced-plugins")]
2889 if let Some(epm) = &enhanced_plugin_manager
2890 && let Some(ref content) = accumulated_content {
2891 let response_for_hook = LlmResponse {
2892 content: Some(content.clone()),
2893 provider_metadata: final_provider_metadata.clone(),
2894 ..Default::default()
2895 };
2896 match epm.run_after_model_call(response_for_hook, ctx.clone() as Arc<dyn CallbackContext>).await {
2897 Ok(adk_plugin::AfterModelCallResult::Continue(modified_response)) => {
2898 accumulated_content = modified_response.content;
2899 if modified_response.provider_metadata.is_some() {
2900 final_provider_metadata = modified_response.provider_metadata;
2901 }
2902 }
2903 Err(e) => {
2904 yield Err(e);
2905 return;
2906 }
2907 }
2908 }
2909
2910 let function_call_names: Vec<String> = accumulated_content.as_ref()
2912 .map(|c| c.parts.iter()
2913 .filter_map(|p| {
2914 if let Part::FunctionCall { name, .. } = p {
2915 Some(name.clone())
2916 } else {
2917 None
2918 }
2919 })
2920 .collect())
2921 .unwrap_or_default();
2922
2923 let has_function_calls = !function_call_names.is_empty();
2924
2925 let all_calls_are_long_running = has_function_calls && function_call_names.iter().all(|name| {
2929 tool_map.get(name)
2930 .map(|t| t.is_long_running())
2931 .unwrap_or(false)
2932 });
2933
2934 if let Some(ref content) = accumulated_content {
2936 conversation_history.push(Self::augment_content_for_history(
2937 content,
2938 final_provider_metadata.as_ref(),
2939 ));
2940
2941 if let Some(ref output_key) = output_key
2943 && !has_function_calls
2944 {
2945 let mut text_parts = String::new();
2946 for part in &content.parts {
2947 if let Part::Text { text } = part {
2948 text_parts.push_str(text);
2949 }
2950 }
2951 if !text_parts.is_empty() {
2952 let mut state_event = Event::new(&invocation_id);
2954 state_event.author = agent_name.clone();
2955 state_event.actions.state_delta.insert(
2956 output_key.clone(),
2957 serde_json::Value::String(text_parts),
2958 );
2959 yield Ok(state_event);
2960 }
2961 }
2962 }
2963
2964 if !has_function_calls {
2965 if let Some(schema) = &prompt_config.output_schema {
2970 let text = accumulated_content
2971 .as_ref()
2972 .map(|c| {
2973 c.parts
2974 .iter()
2975 .filter_map(|p| {
2976 if let Part::Text { text } = p {
2977 Some(text.as_str())
2978 } else {
2979 None
2980 }
2981 })
2982 .collect::<Vec<_>>()
2983 .join("")
2984 })
2985 .unwrap_or_default();
2986
2987 if !text.is_empty()
2988 && let Err(validation_error) = validate_output_against_schema(&text, schema)
2989 {
2990 if schema_retry_count >= output_max_retries {
2991 yield Err(adk_core::AdkError::agent(format!(
2992 "output schema validation failed after {} attempts",
2993 output_max_retries
2994 )));
2995 return;
2996 }
2997 schema_retry_count += 1;
2998
2999 let correction = format!(
3001 "Your output did not match the required schema. Error: {}. Please produce valid JSON matching the schema.",
3002 validation_error
3003 );
3004 conversation_history.push(Content {
3005 role: "user".to_string(),
3006 parts: vec![Part::Text { text: correction }],
3007 });
3008 continue;
3009 }
3010 }
3011
3012 if let Some(ref content) = accumulated_content {
3015 let response_json = trace_json_payload(
3016 content,
3017 ctx.run_config().record_payloads,
3018 ctx.run_config().trace_payload_max_bytes,
3019 );
3020 tracing::Span::current().record("gcp.vertex.agent.llm_response", &response_json);
3021 }
3022
3023 tracing::info!(agent.name = %agent_name, "Agent execution complete");
3024 break;
3025 }
3026
3027 if let Some(content) = &accumulated_content {
3029 let strategy = agent_tool_execution_strategy
3032 .unwrap_or(ToolExecutionStrategy::Sequential);
3033
3034 let fc_parts = collect_function_calls(content, &invocation_id);
3035
3036 let mut transfer_handled = false;
3040 for call in &fc_parts {
3041 if call.name == "transfer_to_agent" {
3042 let target_agent = call
3043 .args
3044 .get("agent_name")
3045 .and_then(|value| value.as_str())
3046 .unwrap_or_default()
3047 .to_string();
3048
3049 let valid_target = valid_transfer_targets.iter().any(|n| n == &target_agent);
3050 if !valid_target {
3051 let error_content = Content {
3052 role: "function".to_string(),
3053 parts: vec![Part::FunctionResponse {
3054 function_response: FunctionResponseData::new(
3055 call.name.clone(),
3056 serde_json::json!({
3057 "error": format!(
3058 "Agent '{}' not found. Available agents: {:?}",
3059 target_agent, valid_transfer_targets
3060 )
3061 }),
3062 ),
3063 id: call.id.clone(),
3064 annotations: None,
3065 }],
3066 };
3067 conversation_history.push(error_content.clone());
3068 let mut error_event = Event::new(&invocation_id);
3069 error_event.author = agent_name.clone();
3070 error_event.llm_response.content = Some(error_content);
3071 yield Ok(error_event);
3072 continue;
3073 }
3074
3075 let mut transfer_event = Event::new(&invocation_id);
3076 transfer_event.author = agent_name.clone();
3077 transfer_event.actions.transfer_to_agent = Some(target_agent);
3078 yield Ok(transfer_event);
3079 transfer_handled = true;
3080 break;
3081 }
3082 }
3083 if transfer_handled {
3084 return;
3085 }
3086
3087 let mut fc_parts: Vec<_> = fc_parts
3089 .into_iter()
3090 .filter(|call| {
3091 if call.name == "transfer_to_agent" {
3092 return false;
3093 }
3094 if let Some(tool) = tool_map.get(&call.name)
3095 && tool.is_builtin()
3096 {
3097 adk_telemetry::debug!(tool.name = %call.name, "skipping built-in tool execution");
3098 return false;
3099 }
3100 true
3101 })
3102 .collect();
3103
3104 for call in &mut fc_parts {
3108 match screen_tool_call(&tool_guardrails, &call.name, &call.args).await {
3109 ToolScreening::Allow(args) => call.args = args,
3110 ToolScreening::Deny(reason) => {
3111 call.guardrail_denial = Some(reason);
3112 }
3113 }
3114 }
3115
3116 let mut confirmation_interrupted = false;
3120 for call in &fc_parts {
3121 if call.guardrail_denial.is_none()
3122 && (tool_confirmation_policy.requires_confirmation(&call.name)
3123 || ctx.requires_tool_confirmation(&call.name))
3124 && static_confirmation_decision(
3125 &confirmation_decisions,
3126 &confirmation_fingerprints,
3127 &call.function_call_id,
3128 &call.name,
3129 &call.args,
3130 )
3131 .is_none()
3132 && live_confirmation_decisions
3133 .get(&call.function_call_id)
3134 .copied()
3135 .is_none()
3136 {
3137 let request = ToolConfirmationRequest {
3138 tool_name: call.name.clone(),
3139 function_call_id: Some(call.function_call_id.clone()),
3140 args: call.args.clone(),
3141 };
3142 if let Some(handler) = confirmation_handler.as_ref() {
3143 match handler.decide(&request).await {
3144 Ok(decision) => {
3145 live_confirmation_decisions
3146 .insert(call.function_call_id.clone(), decision);
3147 continue;
3148 }
3149 Err(error) => {
3150 yield Err(error);
3151 return;
3152 }
3153 }
3154 }
3155
3156 let mut ce = Event::new(&invocation_id);
3157 ce.author = agent_name.clone();
3158 ce.llm_response.interrupted = true;
3159 ce.llm_response.turn_complete = true;
3160 ce.llm_response.content = Some(Content {
3161 role: "model".to_string(),
3162 parts: vec![Part::Text {
3163 text: format!(
3164 "Tool confirmation required for '{}'. Provide approve/deny decision to continue.",
3165 call.name
3166 ),
3167 }],
3168 });
3169 ce.actions.tool_confirmation = Some(request);
3170 yield Ok(ce);
3171 confirmation_interrupted = true;
3172 break;
3173 }
3174 }
3175 if confirmation_interrupted {
3176 return;
3177 }
3178
3179 let cb_mutex = std::sync::Mutex::new(circuit_breaker_state.take());
3181
3182 let concurrency_manager = adk_core::ToolConcurrencyManager::new(
3185 &ctx.run_config().tool_concurrency,
3186 );
3187
3188 let (progress_tx, mut progress_rx) =
3193 tokio::sync::mpsc::channel::<Event>(TOOL_PROGRESS_CAPACITY);
3194
3195 let executor = ToolExecutor {
3196 ctx: ctx.clone(),
3197 tool_map: &tool_map,
3198 tool_retry_budgets: &tool_retry_budgets,
3199 default_retry_budget: &default_retry_budget,
3200 before_tool_callbacks: &before_tool_callbacks,
3201 after_tool_callbacks: &after_tool_callbacks,
3202 after_tool_callbacks_full: &after_tool_callbacks_full,
3203 on_tool_error_callbacks: &on_tool_error_callbacks,
3204 tool_confirmation_policy: &tool_confirmation_policy,
3205 cb_mutex: &cb_mutex,
3206 invocation_id: &invocation_id,
3207 concurrency_manager: &concurrency_manager,
3208 progress_tx: progress_tx.clone(),
3209 tool_timeout,
3210 confirmation_decisions: &confirmation_decisions,
3211 confirmation_fingerprints: &confirmation_fingerprints,
3212 live_confirmation_decisions: &live_confirmation_decisions,
3213 #[cfg(feature = "enhanced-plugins")]
3214 enhanced_plugin_manager: &enhanced_plugin_manager,
3215 };
3216
3217 if ctx.is_cancelled() {
3220 tracing::info!(agent.name = %agent_name, "invocation cancelled before tool dispatch");
3221 return;
3222 }
3223
3224 let mut results = {
3229 let dispatch = async {
3230 let results: Vec<ToolExecutionResult> = match strategy {
3231 ToolExecutionStrategy::Sequential => {
3232 let mut results = Vec::with_capacity(fc_parts.len());
3233 for call in fc_parts {
3234 results.push(executor.execute(call).await);
3235 }
3236 results
3237 }
3238 ToolExecutionStrategy::Parallel => {
3239 use futures::StreamExt as _;
3240 let buffer_size = fc_parts.len().max(1);
3247 futures::stream::iter(
3248 fc_parts.into_iter().map(|call| executor.execute(call)),
3249 )
3250 .buffer_unordered(buffer_size)
3251 .collect()
3252 .await
3253 }
3254 ToolExecutionStrategy::Auto => {
3255 let (concurrent_fcs, sequential_fcs): (Vec<_>, Vec<_>) =
3258 fc_parts.into_iter().partition(|call| {
3259 tool_map.get(&call.name).is_some_and(|tool| {
3260 tool.is_read_only() && tool.is_concurrency_safe()
3261 })
3262 });
3263 let mut all_results = Vec::new();
3264
3265 if !concurrent_fcs.is_empty() {
3268 use futures::StreamExt as _;
3269 let buffer_size = concurrent_fcs.len().max(1);
3270 all_results.extend(
3271 futures::stream::iter(
3272 concurrent_fcs
3273 .into_iter()
3274 .map(|call| executor.execute(call)),
3275 )
3276 .buffer_unordered(buffer_size)
3277 .collect::<Vec<_>>()
3278 .await,
3279 );
3280 }
3281
3282 for call in sequential_fcs {
3284 all_results.push(executor.execute(call).await);
3285 }
3286 all_results
3287 }
3288 };
3289 results
3290 };
3291
3292 tokio::pin!(dispatch);
3298 let results = loop {
3299 tokio::select! {
3300 biased;
3301 Some(progress_event) = progress_rx.recv() => {
3302 yield Ok(progress_event);
3303 }
3304 done = &mut dispatch => break done,
3305 }
3306 };
3307 while let Ok(progress_event) = progress_rx.try_recv() {
3309 yield Ok(progress_event);
3310 }
3311 results
3312 };
3313 results.sort_by_key(|r| r.index);
3315
3316 circuit_breaker_state = cb_mutex.into_inner().unwrap_or_else(|e| e.into_inner());
3318
3319 for result in results {
3321 let mut tool_event = Event::new(&invocation_id);
3322 tool_event.author = agent_name.clone();
3323 tool_event.actions = result.actions;
3324 tool_event.llm_response.content = Some(result.content.clone());
3325 yield Ok(tool_event);
3326
3327 if result.escalate_or_skip {
3328 return;
3329 }
3330
3331 conversation_history.push(result.content);
3332 }
3333
3334 let refreshed_tools = match tool_setup.resolve(&ctx).await {
3339 Ok(tools) => tools,
3340 Err(error) => {
3341 yield Err(error);
3342 return;
3343 }
3344 };
3345 tool_map = refreshed_tools.map;
3346 tool_declarations = refreshed_tools.declarations;
3347 valid_transfer_targets = refreshed_tools.transfer_targets;
3348 }
3349
3350 if all_calls_are_long_running {
3354 }
3358 }
3359
3360 for callback in after_agent_callbacks.as_ref() {
3363 match callback(ctx.clone() as Arc<dyn CallbackContext>).await {
3364 Ok(Some(content)) => {
3365 let mut after_event = Event::new(&invocation_id);
3367 after_event.author = agent_name.clone();
3368 after_event.llm_response.content = Some(content);
3369 yield Ok(after_event);
3370 break; }
3372 Ok(None) => {
3373 continue;
3375 }
3376 Err(e) => {
3377 yield Err(e);
3379 return;
3380 }
3381 }
3382 }
3383 };
3384
3385 Ok(Box::pin(s))
3386 }
3387}
3388
3389#[cfg(test)]
3390mod run_helper_tests {
3391 use super::*;
3392
3393 #[test]
3394 fn generation_config_layers_schema_and_cached_content() {
3395 let base =
3396 adk_core::GenerateContentConfig { temperature: Some(0.25), ..Default::default() };
3397 let schema = serde_json::json!({"type": "object"});
3398
3399 let config = build_generation_config(Some(&base), Some(&schema), Some("cached/example"))
3400 .expect("config should be present");
3401
3402 assert_eq!(config.temperature, Some(0.25));
3403 assert_eq!(config.response_schema, Some(schema));
3404 assert_eq!(config.cached_content.as_deref(), Some("cached/example"));
3405 }
3406
3407 #[test]
3408 fn function_calls_preserve_order_and_create_fallback_ids() {
3409 let content = Content {
3410 role: "model".to_string(),
3411 parts: vec![
3412 Part::Text { text: "before".to_string() },
3413 Part::FunctionCall {
3414 name: "first".to_string(),
3415 args: serde_json::json!({"value": 1}),
3416 id: None,
3417 thought_signature: None,
3418 },
3419 Part::FunctionCall {
3420 name: "second".to_string(),
3421 args: serde_json::json!({"value": 2}),
3422 id: Some("provider-id".to_string()),
3423 thought_signature: None,
3424 },
3425 ],
3426 };
3427
3428 let calls = collect_function_calls(&content, "invocation");
3429
3430 assert_eq!(calls.len(), 2);
3431 assert_eq!(calls[0].index, 0);
3432 assert_eq!(calls[0].name, "first");
3433 assert_eq!(calls[0].function_call_id, "invocation_first_0");
3434 assert_eq!(calls[1].index, 1);
3435 assert_eq!(calls[1].name, "second");
3436 assert_eq!(calls[1].function_call_id, "provider-id");
3437 }
3438}