adk_tool/
function_tool.rs1use adk_core::{Result, Tool, ToolContext};
2use async_trait::async_trait;
3use schemars::{schema::RootSchema, JsonSchema};
4use serde::Serialize;
5use serde_json::Value;
6use std::future::Future;
7use std::pin::Pin;
8use std::sync::Arc;
9
10type AsyncHandler = Box<
11 dyn Fn(Arc<dyn ToolContext>, Value) -> Pin<Box<dyn Future<Output = Result<Value>> + Send>>
12 + Send
13 + Sync,
14>;
15
16pub struct FunctionTool {
17 name: String,
18 description: String,
19 handler: AsyncHandler,
20 long_running: bool,
21 parameters_schema: Option<Value>,
22 response_schema: Option<Value>,
23}
24
25impl FunctionTool {
26 pub fn new<F, Fut>(name: impl Into<String>, description: impl Into<String>, handler: F) -> Self
27 where
28 F: Fn(Arc<dyn ToolContext>, Value) -> Fut + Send + Sync + 'static,
29 Fut: Future<Output = Result<Value>> + Send + 'static,
30 {
31 Self {
32 name: name.into(),
33 description: description.into(),
34 handler: Box::new(move |ctx, args| Box::pin(handler(ctx, args))),
35 long_running: false,
36 parameters_schema: None,
37 response_schema: None,
38 }
39 }
40
41 pub fn with_long_running(mut self, long_running: bool) -> Self {
42 self.long_running = long_running;
43 self
44 }
45
46 pub fn with_parameters_schema<T>(mut self) -> Self
47 where
48 T: JsonSchema + Serialize,
49 {
50 self.parameters_schema = Some(generate_schema::<T>());
51 self
52 }
53
54 pub fn with_response_schema<T>(mut self) -> Self
55 where
56 T: JsonSchema + Serialize,
57 {
58 self.response_schema = Some(generate_schema::<T>());
59 self
60 }
61
62 pub fn parameters_schema(&self) -> Option<&Value> {
63 self.parameters_schema.as_ref()
64 }
65
66 pub fn response_schema(&self) -> Option<&Value> {
67 self.response_schema.as_ref()
68 }
69}
70
71const LONG_RUNNING_NOTE: &str = "NOTE: This is a long-running operation. Do not call this tool again if it has already returned some intermediate or pending status.";
73
74#[async_trait]
75impl Tool for FunctionTool {
76 fn name(&self) -> &str {
77 &self.name
78 }
79
80 fn description(&self) -> &str {
81 &self.description
82 }
83
84 fn enhanced_description(&self) -> String {
87 if self.long_running {
88 if self.description.is_empty() {
89 LONG_RUNNING_NOTE.to_string()
90 } else {
91 format!("{}\n\n{}", self.description, LONG_RUNNING_NOTE)
92 }
93 } else {
94 self.description.clone()
95 }
96 }
97
98 fn is_long_running(&self) -> bool {
99 self.long_running
100 }
101
102 fn parameters_schema(&self) -> Option<Value> {
103 self.parameters_schema.clone()
104 }
105
106 fn response_schema(&self) -> Option<Value> {
107 self.response_schema.clone()
108 }
109
110 #[adk_telemetry::instrument(
111 skip(self, ctx, args),
112 fields(
113 tool.name = %self.name,
114 tool.description = %self.description,
115 tool.long_running = %self.long_running,
116 function_call.id = %ctx.function_call_id()
117 )
118 )]
119 async fn execute(&self, ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value> {
120 adk_telemetry::debug!("Executing tool");
121 (self.handler)(ctx, args).await
122 }
123}
124
125fn generate_schema<T>() -> Value
126where
127 T: JsonSchema + Serialize,
128{
129 let settings = schemars::gen::SchemaSettings::openapi3().with(|s| {
130 s.inline_subschemas = true;
131 s.meta_schema = None;
132 });
133 let generator = schemars::gen::SchemaGenerator::new(settings);
134 let mut schema: RootSchema = generator.into_root_schema_for::<T>();
135 schema.schema.metadata().title = None;
136 serde_json::to_value(schema).unwrap()
137}