1#[macro_export]
31macro_rules! agent {
32 (@build $b:expr $(,)?) => { $b.build() };
34
35 (@build $b:expr, model: $v:expr, $($rest:tt)*) => {
36 $crate::agent!(@build $b.model($v), $($rest)*)
37 };
38 (@build $b:expr, system_prompt: $v:expr, $($rest:tt)*) => {
39 $crate::agent!(@build $b.system_prompt($v), $($rest)*)
40 };
41 (@build $b:expr, name: $v:expr, $($rest:tt)*) => {
42 $crate::agent!(@build $b.name($v), $($rest)*)
43 };
44 (@build $b:expr, max_iterations: $v:expr, $($rest:tt)*) => {
45 $crate::agent!(@build $b.max_iterations($v), $($rest)*)
46 };
47 (@build $b:expr, token_limit: $v:expr, $($rest:tt)*) => {
48 $crate::agent!(@build $b.token_limit($v), $($rest)*)
49 };
50 (@build $b:expr, llm_config: $v:expr, $($rest:tt)*) => {
51 $crate::agent!(@build $b.llm_config($v), $($rest)*)
52 };
53 (@build $b:expr, session_id: $v:expr, $($rest:tt)*) => {
54 $crate::agent!(@build $b.session_id($v), $($rest)*)
55 };
56 (@build $b:expr, conversation_id: $v:expr, $($rest:tt)*) => {
57 $crate::agent!(@build $b.conversation_id($v), $($rest)*)
58 };
59 (@build $b:expr, enable_memory: $v:expr, $($rest:tt)*) => {
60 $crate::agent!(@build { if $v { $b.enable_memory() } else { $b } }, $($rest)*)
61 };
62 (@build $b:expr, enable_cot: $v:expr, $($rest:tt)*) => {
63 $crate::agent!(@build { if $v { $b.enable_cot() } else { $b.disable_cot() } }, $($rest)*)
64 };
65 (@build $b:expr, permission_policy: $v:expr, $($rest:tt)*) => {
66 $crate::agent!(@build $b.permission_policy(::std::sync::Arc::new($v)), $($rest)*)
67 };
68 (@build $b:expr, audit_logger: $v:expr, $($rest:tt)*) => {
69 $crate::agent!(@build $b.audit_logger(::std::sync::Arc::new($v)), $($rest)*)
70 };
71 (@build $b:expr, tools: [$($t:expr),* $(,)?], $($rest:tt)*) => {
72 $crate::agent!(@build {
73 let mut __b = $b.enable_tools();
74 $( __b = __b.tool(Box::new($t)); )*
75 __b
76 }, $($rest)*)
77 };
78 (@build $b:expr, callbacks: [$($c:expr),* $(,)?], $($rest:tt)*) => {
79 $crate::agent!(@build {
80 let mut __b = $b;
81 $( __b = __b.callback(::std::sync::Arc::new($c)); )*
82 __b
83 }, $($rest)*)
84 };
85 (@build $b:expr, guards: [$($g:expr),* $(,)?], $($rest:tt)*) => {
86 $crate::agent!(@build {
87 let mut __b = $b;
88 $( __b = __b.guard(::std::sync::Arc::new($g)); )*
89 __b
90 }, $($rest)*)
91 };
92
93 ( $($body:tt)* ) => {
95 $crate::agent!(@build $crate::agent::ReactAgentBuilder::new(), $($body)*)
96 };
97}
98
99#[macro_export]
119macro_rules! messages {
120 ( $( $role:ident($content:expr) ),* $(,)? ) => {
121 vec![
122 $( $crate::llm::types::Message::$role($content.to_string()) ),*
123 ]
124 };
125}
126
127#[macro_export]
140macro_rules! tool_params {
141 ( $( $name:literal => $spec:tt ),* $(,)? ) => {{
142 let mut __properties = ::serde_json::Map::new();
143 let mut __required: Vec<&str> = Vec::new();
144 $( $crate::__tool_param_field!(__properties, __required, $name, $spec); )*
145 ::serde_json::json!({
146 "type": "object",
147 "properties": ::serde_json::Value::Object(__properties),
148 "required": __required,
149 })
150 }};
151}
152
153#[doc(hidden)]
154#[macro_export]
155macro_rules! __tool_param_field {
156 ($props:expr, $req:expr, $name:literal, ($ty:ident, required, $desc:literal)) => {
157 let mut __p = ::serde_json::Map::new();
158 __p.insert(
159 "type".to_string(),
160 ::serde_json::Value::String(stringify!($ty).to_string()),
161 );
162 __p.insert(
163 "description".to_string(),
164 ::serde_json::Value::String($desc.to_string()),
165 );
166 $props.insert($name.to_string(), ::serde_json::Value::Object(__p));
167 $req.push($name);
168 };
169 ($props:expr, $req:expr, $name:literal, ($ty:ident, required)) => {
170 let mut __p = ::serde_json::Map::new();
171 __p.insert(
172 "type".to_string(),
173 ::serde_json::Value::String(stringify!($ty).to_string()),
174 );
175 $props.insert($name.to_string(), ::serde_json::Value::Object(__p));
176 $req.push($name);
177 };
178 ($props:expr, $req:expr, $name:literal, ($ty:ident, $desc:literal)) => {
179 let mut __p = ::serde_json::Map::new();
180 __p.insert(
181 "type".to_string(),
182 ::serde_json::Value::String(stringify!($ty).to_string()),
183 );
184 __p.insert(
185 "description".to_string(),
186 ::serde_json::Value::String($desc.to_string()),
187 );
188 $props.insert($name.to_string(), ::serde_json::Value::Object(__p));
189 };
190 ($props:expr, $req:expr, $name:literal, ($ty:ident)) => {
191 let mut __p = ::serde_json::Map::new();
192 __p.insert(
193 "type".to_string(),
194 ::serde_json::Value::String(stringify!($ty).to_string()),
195 );
196 $props.insert($name.to_string(), ::serde_json::Value::Object(__p));
197 };
198}
199
200#[macro_export]
215macro_rules! chat_request {
216 ( messages: [$( $role:ident($content:expr) ),* $(,)?] $(, $key:ident : $val:expr)* $(,)? ) => {{
217 #[allow(unused_mut)]
218 let mut req = $crate::llm::ChatRequest {
219 messages: vec![
220 $( $crate::llm::types::Message::$role($content.to_string()) ),*
221 ],
222 ..Default::default()
223 };
224 $( $crate::__chat_request_field!(req, $key, $val); )*
225 req
226 }};
227}
228
229#[doc(hidden)]
230#[macro_export]
231macro_rules! __chat_request_field {
232 ($req:expr, temperature, $v:expr) => {
233 $req.temperature = Some($v);
234 };
235 ($req:expr, max_tokens, $v:expr) => {
236 $req.max_tokens = Some($v as u32);
237 };
238 ($req:expr, tool_choice, $v:expr) => {
239 $req.tool_choice = Some($v.to_string());
240 };
241}
242
243#[cfg(test)]
244mod tests {
245 use crate::llm::types::{Message, Role};
246
247 #[test]
248 fn messages_macro_basic() {
249 let msgs = messages![
250 system("You are an assistant"),
251 user("Hello"),
252 assistant("Hello! How can I help you?"),
253 ];
254
255 assert_eq!(msgs.len(), 3);
256 assert_eq!(msgs[0].role, Role::System);
257 assert_eq!(msgs[0].content.as_text_ref(), Some("You are an assistant"));
258 assert_eq!(msgs[1].role, Role::User);
259 assert_eq!(msgs[2].role, Role::Assistant);
260 }
261
262 #[test]
263 fn messages_macro_single() {
264 let msgs = messages![user("hello")];
265 assert_eq!(msgs.len(), 1);
266 assert_eq!(msgs[0].role, Role::User);
267 }
268
269 #[test]
270 fn messages_macro_empty() {
271 let msgs: Vec<Message> = messages![];
272 assert!(msgs.is_empty());
273 }
274
275 #[test]
276 fn tool_params_macro_basic() {
277 let schema = tool_params! {
278 "expression" => (string, required, "Math expression"),
279 "precision" => (number, "Decimal precision"),
280 };
281
282 let obj = schema.as_object().unwrap();
283 assert_eq!(obj["type"], "object");
284
285 let props = obj["properties"].as_object().unwrap();
286 assert!(props.contains_key("expression"));
287 assert!(props.contains_key("precision"));
288
289 let expr_prop = props["expression"].as_object().unwrap();
290 assert_eq!(expr_prop["type"], "string");
291 assert_eq!(expr_prop["description"], "Math expression");
292
293 let required = obj["required"].as_array().unwrap();
294 assert_eq!(required.len(), 1);
295 assert_eq!(required[0], "expression");
296 }
297
298 #[test]
299 fn tool_params_macro_all_required() {
300 let schema = tool_params! {
301 "a" => (number, required, "param a"),
302 "b" => (number, required, "param b"),
303 };
304 let required = schema["required"].as_array().unwrap();
305 assert_eq!(required.len(), 2);
306 }
307
308 #[test]
309 fn tool_params_macro_none_required() {
310 let schema = tool_params! {
311 "hint" => (string, "optional hint"),
312 };
313 let required = schema["required"].as_array().unwrap();
314 assert!(required.is_empty());
315 }
316
317 #[test]
318 fn chat_request_macro_basic() {
319 let req = chat_request!(
320 messages: [system("You are an assistant"), user("Hello")],
321 temperature: 0.7,
322 max_tokens: 2048,
323 );
324
325 assert_eq!(req.messages.len(), 2);
326 assert_eq!(req.messages[0].role, Role::System);
327 assert_eq!(req.temperature, Some(0.7));
328 assert_eq!(req.max_tokens, Some(2048));
329 }
330
331 #[test]
332 fn chat_request_macro_no_options() {
333 let req = chat_request!(
334 messages: [user("hello")],
335 );
336
337 assert_eq!(req.messages.len(), 1);
338 assert_eq!(req.temperature, None);
339 assert_eq!(req.max_tokens, None);
340 }
341
342 #[test]
343 fn agent_macro_basic() {
344 let result = agent! {
345 model: "test-model",
346 system_prompt: "You are an assistant",
347 };
348 assert!(result.is_ok());
349 }
350
351 #[test]
352 fn agent_macro_with_tools_and_options() {
353 use crate::tools::builtin::answer::FinalAnswerTool;
354
355 let result = agent! {
356 model: "test-model",
357 system_prompt: "You are a calculation assistant",
358 name: "calc",
359 tools: [FinalAnswerTool],
360 max_iterations: 5,
361 };
362 assert!(result.is_ok());
363
364 let agent = result.unwrap();
365 assert!(agent.tool_names().contains(&String::from("final_answer")));
366 }
367}