|
1 | 1 | //! Procedural macros for the radkit agent framework. |
2 | 2 | //! |
3 | | -//! This crate provides the `#[skill]` attribute macro for defining A2A-compliant skills. |
| 3 | +//! This crate provides attribute macros for defining A2A-compliant skills and tools. |
4 | 4 |
|
5 | 5 | #![deny(unsafe_code, unreachable_patterns, unused_must_use)] |
6 | 6 | #![warn(clippy::all, clippy::pedantic, clippy::nursery)] |
7 | 7 | #![allow(clippy::module_name_repetitions)] // Common pattern in proc macro crates |
8 | 8 |
|
9 | 9 | mod skill; |
| 10 | +mod tool; |
10 | 11 | mod validation; |
11 | 12 |
|
12 | 13 | use proc_macro::TokenStream; |
@@ -92,3 +93,73 @@ pub fn skill(attr: TokenStream, item: TokenStream) -> TokenStream { |
92 | 93 |
|
93 | 94 | skill::generate_skill_impl(args, item).into() |
94 | 95 | } |
| 96 | + |
| 97 | +/// Attribute macro for defining tools with automatic parameter extraction. |
| 98 | +/// |
| 99 | +/// This macro generates a zero-sized struct with the function name and implements |
| 100 | +/// the `BaseTool` trait directly, eliminating manual parameter extraction and JSON schema construction. |
| 101 | +/// |
| 102 | +/// The function name is used as the tool name, so choose function names that accurately |
| 103 | +/// describe the tool's purpose. |
| 104 | +/// |
| 105 | +/// # Required Parameters |
| 106 | +/// |
| 107 | +/// - `description`: A detailed description of what the tool does (String) |
| 108 | +/// |
| 109 | +/// # Example |
| 110 | +/// |
| 111 | +/// ```ignore |
| 112 | +/// use radkit::tools::{ToolResult, ToolContext}; |
| 113 | +/// use radkit_macros::tool; |
| 114 | +/// use serde::{Deserialize}; |
| 115 | +/// use schemars::JsonSchema; |
| 116 | +/// use serde_json::json; |
| 117 | +/// |
| 118 | +/// #[derive(Deserialize, JsonSchema)] |
| 119 | +/// struct AddArgs { |
| 120 | +/// a: i64, |
| 121 | +/// b: i64, |
| 122 | +/// } |
| 123 | +/// |
| 124 | +/// #[tool(description = "Add two numbers")] |
| 125 | +/// async fn add(args: AddArgs) -> ToolResult { |
| 126 | +/// ToolResult::success(json!({"sum": args.a + args.b})) |
| 127 | +/// } |
| 128 | +/// |
| 129 | +/// // With ToolContext |
| 130 | +/// #[derive(Deserialize, JsonSchema)] |
| 131 | +/// struct SaveArgs { |
| 132 | +/// key: String, |
| 133 | +/// value: String, |
| 134 | +/// } |
| 135 | +/// |
| 136 | +/// #[tool(description = "Save state")] |
| 137 | +/// async fn save_state(args: SaveArgs, ctx: &ToolContext<'_>) -> ToolResult { |
| 138 | +/// ctx.state().set_state(&args.key, json!(args.value)); |
| 139 | +/// ToolResult::success(json!({"saved": true})) |
| 140 | +/// } |
| 141 | +/// ``` |
| 142 | +/// |
| 143 | +/// # Generated Code |
| 144 | +/// |
| 145 | +/// The macro transforms the async function into a zero-sized struct that implements |
| 146 | +/// `BaseTool`. Parameters are automatically deserialized using serde and the JSON |
| 147 | +/// schema is generated using schemars. The function name becomes both the struct |
| 148 | +/// name and the tool name visible to the LLM. |
| 149 | +/// |
| 150 | +/// # Usage |
| 151 | +/// |
| 152 | +/// ```ignore |
| 153 | +/// // Pass the tool struct directly to with_tool() - no function call! |
| 154 | +/// let worker = LlmWorker::builder(llm) |
| 155 | +/// .with_tool(add) // ← Not add() |
| 156 | +/// .with_tool(save_state) // ← Not save_state() |
| 157 | +/// .build(); |
| 158 | +/// ``` |
| 159 | +#[proc_macro_attribute] |
| 160 | +pub fn tool(attr: TokenStream, item: TokenStream) -> TokenStream { |
| 161 | + let args = parse_macro_input!(attr as tool::ToolArgs); |
| 162 | + let item = proc_macro2::TokenStream::from(item); |
| 163 | + |
| 164 | + tool::generate_tool_impl(args, item).into() |
| 165 | +} |
0 commit comments