adk_tool/
function_tool.rs1use adk_core::{Result, Tool, ToolContext};
2use async_trait::async_trait;
3use schemars::{
4 JsonSchema,
5 generate::{SchemaGenerator, SchemaSettings},
6};
7use serde::Serialize;
8use serde_json::Value;
9use std::future::Future;
10use std::pin::Pin;
11use std::sync::Arc;
12
13type AsyncHandler = Box<
14 dyn Fn(Arc<dyn ToolContext>, Value) -> Pin<Box<dyn Future<Output = Result<Value>> + Send>>
15 + Send
16 + Sync,
17>;
18
19pub struct FunctionTool {
25 name: String,
26 description: String,
27 handler: AsyncHandler,
28 long_running: bool,
29 read_only: bool,
30 concurrency_safe: bool,
31 parameters_schema: Option<Value>,
32 response_schema: Option<Value>,
33 scopes: Vec<&'static str>,
34}
35
36impl FunctionTool {
37 pub fn new<F, Fut>(name: impl Into<String>, description: impl Into<String>, handler: F) -> Self
39 where
40 F: Fn(Arc<dyn ToolContext>, Value) -> Fut + Send + Sync + 'static,
41 Fut: Future<Output = Result<Value>> + Send + 'static,
42 {
43 Self {
44 name: name.into(),
45 description: description.into(),
46 handler: Box::new(move |ctx, args| Box::pin(handler(ctx, args))),
47 long_running: false,
48 read_only: false,
49 concurrency_safe: false,
50 parameters_schema: None,
51 response_schema: None,
52 scopes: Vec::new(),
53 }
54 }
55
56 pub fn with_long_running(mut self, long_running: bool) -> Self {
58 self.long_running = long_running;
59 self
60 }
61
62 pub fn with_read_only(mut self, read_only: bool) -> Self {
64 self.read_only = read_only;
65 self
66 }
67
68 pub fn with_concurrency_safe(mut self, concurrency_safe: bool) -> Self {
70 self.concurrency_safe = concurrency_safe;
71 self
72 }
73
74 pub fn with_parameters_schema<T>(mut self) -> Self
76 where
77 T: JsonSchema + Serialize,
78 {
79 self.parameters_schema = Some(generate_schema::<T>());
80 self
81 }
82
83 pub fn with_response_schema<T>(mut self) -> Self
85 where
86 T: JsonSchema + Serialize,
87 {
88 self.response_schema = Some(generate_schema::<T>());
89 self
90 }
91
92 pub fn with_scopes(mut self, scopes: &[&'static str]) -> Self {
104 self.scopes = scopes.to_vec();
105 self
106 }
107
108 pub fn parameters_schema(&self) -> Option<&Value> {
110 self.parameters_schema.as_ref()
111 }
112
113 pub fn response_schema(&self) -> Option<&Value> {
115 self.response_schema.as_ref()
116 }
117}
118
119const 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.";
121
122#[async_trait]
123impl Tool for FunctionTool {
124 fn name(&self) -> &str {
125 &self.name
126 }
127
128 fn description(&self) -> &str {
129 &self.description
130 }
131
132 fn enhanced_description(&self) -> String {
135 if self.long_running {
136 if self.description.is_empty() {
137 LONG_RUNNING_NOTE.to_string()
138 } else {
139 format!("{}\n\n{}", self.description, LONG_RUNNING_NOTE)
140 }
141 } else {
142 self.description.clone()
143 }
144 }
145
146 fn is_long_running(&self) -> bool {
147 self.long_running
148 }
149
150 fn is_read_only(&self) -> bool {
151 self.read_only
152 }
153
154 fn is_concurrency_safe(&self) -> bool {
155 self.concurrency_safe
156 }
157
158 fn parameters_schema(&self) -> Option<Value> {
159 self.parameters_schema.clone()
160 }
161
162 fn response_schema(&self) -> Option<Value> {
163 self.response_schema.clone()
164 }
165
166 fn required_scopes(&self) -> &[&str] {
167 &self.scopes
168 }
169
170 #[adk_telemetry::instrument(
171 skip(self, ctx, args),
172 fields(
173 tool.name = %self.name,
174 tool.description = %self.description,
175 tool.long_running = %self.long_running,
176 function_call.id = %ctx.function_call_id()
177 )
178 )]
179 async fn execute(&self, ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value> {
180 adk_telemetry::debug!("Executing tool");
181 (self.handler)(ctx, args).await
182 }
183}
184
185fn generate_schema<T>() -> Value
186where
187 T: JsonSchema + Serialize,
188{
189 let settings = SchemaSettings::openapi3().with(|s| {
190 s.inline_subschemas = true;
191 s.meta_schema = None;
192 });
193 let generator = SchemaGenerator::new(settings);
194 let mut schema = generator.into_root_schema_for::<T>();
195 if let Some(object) = schema.as_object_mut() {
196 object.remove("title");
197 }
198 serde_json::to_value(schema).unwrap()
199}